diff --git a/apps/vben5/apps/app-antd/package.json b/apps/vben5/apps/app-antd/package.json index be1116864..adfaf25a1 100644 --- a/apps/vben5/apps/app-antd/package.json +++ b/apps/vben5/apps/app-antd/package.json @@ -29,6 +29,7 @@ "@abp/account": "workspace:*", "@abp/auditing": "workspace:*", "@abp/core": "workspace:*", + "@abp/features": "workspace:*", "@abp/identity": "workspace:*", "@abp/notifications": "workspace:*", "@abp/openiddict": "workspace:*", diff --git a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json index af1be1952..b5b1400f0 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json @@ -30,6 +30,11 @@ "groups": "Groups", "definitions": "Definitions" }, + "features": { + "title": "Features", + "groups": "Groups", + "definitions": "Definitions" + }, "settings": { "title": "Settings", "definitions": "Definitions", diff --git a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json index 1c4595dbb..6cc746f9c 100644 --- a/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json +++ b/apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json @@ -30,6 +30,11 @@ "groups": "权限分组", "definitions": "权限定义" }, + "features": { + "title": "功能管理", + "groups": "功能分组", + "definitions": "功能定义" + }, "settings": { "title": "设置管理", "definitions": "设置定义", diff --git a/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts b/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts index f737035c3..b0c6ea5fc 100644 --- a/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts +++ b/apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts @@ -117,6 +117,35 @@ const routes: RouteRecordRaw[] = [ }, ], }, + { + meta: { + title: $t('abp.manage.features.title'), + icon: 'ant-design:gold-outlined', + }, + name: 'FeatureManagement', + path: '/manage/features', + children: [ + { + meta: { + title: $t('abp.manage.features.groups'), + icon: 'lucide:group', + }, + name: 'FeatureGroupDefinitions', + path: '/manage/features/groups', + component: () => import('#/views/features/groups/index.vue'), + }, + { + meta: { + title: $t('abp.manage.features.definitions'), + icon: 'pajamas:feature-flag', + }, + name: 'FeatureDefinitions', + path: '/manage/features/definitions', + component: () => + import('#/views/features/definitions/index.vue'), + }, + ], + }, { meta: { title: $t('abp.manage.settings.title'), diff --git a/apps/vben5/apps/app-antd/src/views/features/definitions/index.vue b/apps/vben5/apps/app-antd/src/views/features/definitions/index.vue new file mode 100644 index 000000000..637b08c87 --- /dev/null +++ b/apps/vben5/apps/app-antd/src/views/features/definitions/index.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/apps/vben5/apps/app-antd/src/views/features/groups/index.vue b/apps/vben5/apps/app-antd/src/views/features/groups/index.vue new file mode 100644 index 000000000..caa12d1e8 --- /dev/null +++ b/apps/vben5/apps/app-antd/src/views/features/groups/index.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/apps/vben5/packages/@abp/core/src/hooks/useValidation.ts b/apps/vben5/packages/@abp/core/src/hooks/useValidation.ts index afb24569e..4e18afba0 100644 --- a/apps/vben5/packages/@abp/core/src/hooks/useValidation.ts +++ b/apps/vben5/packages/@abp/core/src/hooks/useValidation.ts @@ -129,7 +129,7 @@ export function useValidation(): RuleCreator { required?: boolean, ): Rule { const message = field.name - ? L(useNameEnum, [_getFieldName(field), field.minimum, field.maximum]) + ? L(useNameEnum, [_getFieldName(field), field.maximum, field.minimum]) : L(notNameEnum, [field.minimum, field.maximum]); return { message, @@ -397,6 +397,12 @@ export function useValidation(): RuleCreator { ), ]; }, + mapEnumValidMessage( + enumName: string, + args?: any[] | Record | undefined, + ) { + return L(enumName, args); + }, }; return ruleCreator; diff --git a/apps/vben5/packages/@abp/core/src/types/rules.ts b/apps/vben5/packages/@abp/core/src/types/rules.ts index aef6ed573..3737cc5fb 100644 --- a/apps/vben5/packages/@abp/core/src/types/rules.ts +++ b/apps/vben5/packages/@abp/core/src/types/rules.ts @@ -45,6 +45,11 @@ interface RuleCreator { fieldOnlyAcceptsFilesExtensions(field: FieldContains): Rule[]; /** 字段{0}不可为空 */ fieldRequired(field: Field): Rule[]; + /** 获取一个错误枚举验证消息 */ + mapEnumValidMessage( + enumName: string, + args?: any[] | Record | undefined, + ): string; } export type { RuleCreator }; diff --git a/apps/vben5/packages/@abp/core/src/utils/index.ts b/apps/vben5/packages/@abp/core/src/utils/index.ts index 2109114a1..1219f2544 100644 --- a/apps/vben5/packages/@abp/core/src/utils/index.ts +++ b/apps/vben5/packages/@abp/core/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './date'; +export * from './is'; export * from './mitt'; export * from './regex'; export * from './string'; diff --git a/apps/vben5/packages/@abp/features/package.json b/apps/vben5/packages/@abp/features/package.json new file mode 100644 index 000000000..c44657431 --- /dev/null +++ b/apps/vben5/packages/@abp/features/package.json @@ -0,0 +1,39 @@ +{ + "name": "@abp/features", + "version": "9.0.4", + "homepage": "https://github.com/colinin/abp-next-admin", + "bugs": "https://github.com/colinin/abp-next-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/colinin/abp-next-admin.git", + "directory": "packages/@abp/features" + }, + "license": "MIT", + "type": "module", + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "dependencies": { + "@abp/core": "workspace:*", + "@abp/request": "workspace:*", + "@abp/ui": "workspace:*", + "@ant-design/icons-vue": "catalog:", + "@vben/access": "workspace:*", + "@vben/common-ui": "workspace:*", + "@vben/hooks": "workspace:*", + "@vben/icons": "workspace:*", + "@vben/locales": "workspace:*", + "@vben/utils": "workspace:*", + "ant-design-vue": "catalog:", + "dayjs": "catalog:", + "vue": "catalog:*", + "vxe-table": "catalog:" + }, + "devDependencies": {} +} diff --git a/apps/vben5/packages/@abp/features/src/api/index.ts b/apps/vben5/packages/@abp/features/src/api/index.ts new file mode 100644 index 000000000..4287d4768 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/api/index.ts @@ -0,0 +1,3 @@ +export { useFeatureDefinitionsApi } from './useFeatureDefinitionsApi'; +export { useFeatureGroupDefinitionsApi } from './useFeatureGroupDefinitionsApi'; +export { useFeaturesApi } from './useFeaturesApi'; diff --git a/apps/vben5/packages/@abp/features/src/api/useFeatureDefinitionsApi.ts b/apps/vben5/packages/@abp/features/src/api/useFeatureDefinitionsApi.ts new file mode 100644 index 000000000..165af3aff --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/api/useFeatureDefinitionsApi.ts @@ -0,0 +1,100 @@ +import type { ListResultDto } from '@abp/core'; + +import type { + FeatureDefinitionCreateDto, + FeatureDefinitionDto, + FeatureDefinitionGetListInput, + FeatureDefinitionUpdateDto, +} from '../types/definitions'; + +import { useRequest } from '@abp/request'; + +export function useFeatureDefinitionsApi() { + const { cancel, request } = useRequest(); + + /** + * 删除功能定义 + * @param name 功能名称 + */ + function deleteApi(name: string): Promise { + return request(`/api/feature-management/definitions/${name}`, { + method: 'DELETE', + }); + } + + /** + * 查询功能定义 + * @param name 功能名称 + * @returns 功能定义数据传输对象 + */ + function getApi(name: string): Promise { + return request( + `/api/feature-management/definitions/${name}`, + { + method: 'GET', + }, + ); + } + + /** + * 查询功能定义列表 + * @param input 功能过滤条件 + * @returns 功能定义数据传输对象列表 + */ + function getListApi( + input?: FeatureDefinitionGetListInput, + ): Promise> { + return request>( + `/api/feature-management/definitions`, + { + method: 'GET', + params: input, + }, + ); + } + + /** + * 创建功能定义 + * @param input 功能定义参数 + * @returns 功能定义数据传输对象 + */ + function createApi( + input: FeatureDefinitionCreateDto, + ): Promise { + return request( + '/api/feature-management/definitions', + { + data: input, + method: 'POST', + }, + ); + } + + /** + * 更新功能定义 + * @param name 功能名称 + * @param input 功能定义参数 + * @returns 功能定义数据传输对象 + */ + function updateApi( + name: string, + input: FeatureDefinitionUpdateDto, + ): Promise { + return request( + `/api/feature-management/definitions/${name}`, + { + data: input, + method: 'PUT', + }, + ); + } + + return { + cancel, + createApi, + deleteApi, + getApi, + getListApi, + updateApi, + }; +} diff --git a/apps/vben5/packages/@abp/features/src/api/useFeatureGroupDefinitionsApi.ts b/apps/vben5/packages/@abp/features/src/api/useFeatureGroupDefinitionsApi.ts new file mode 100644 index 000000000..a3f19ef6c --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/api/useFeatureGroupDefinitionsApi.ts @@ -0,0 +1,100 @@ +import type { ListResultDto } from '@abp/core'; + +import type { + FeatureGroupDefinitionCreateDto, + FeatureGroupDefinitionDto, + FeatureGroupDefinitionGetListInput, + FeatureGroupDefinitionUpdateDto, +} from '../types/groups'; + +import { useRequest } from '@abp/request'; + +export function useFeatureGroupDefinitionsApi() { + const { cancel, request } = useRequest(); + + /** + * 删除功能定义 + * @param name 功能名称 + */ + function deleteApi(name: string): Promise { + return request(`/api/feature-management/definitions/groups/${name}`, { + method: 'DELETE', + }); + } + + /** + * 查询功能定义 + * @param name 功能名称 + * @returns 功能定义数据传输对象 + */ + function getApi(name: string): Promise { + return request( + `/api/feature-management/definitions/groups/${name}`, + { + method: 'GET', + }, + ); + } + + /** + * 查询功能定义列表 + * @param input 功能过滤条件 + * @returns 功能定义数据传输对象列表 + */ + function getListApi( + input?: FeatureGroupDefinitionGetListInput, + ): Promise> { + return request>( + `/api/feature-management/definitions/groups`, + { + method: 'GET', + params: input, + }, + ); + } + + /** + * 创建功能定义 + * @param input 功能定义参数 + * @returns 功能定义数据传输对象 + */ + function createApi( + input: FeatureGroupDefinitionCreateDto, + ): Promise { + return request( + '/api/feature-management/definitions/groups', + { + data: input, + method: 'POST', + }, + ); + } + + /** + * 更新功能定义 + * @param name 功能名称 + * @param input 功能定义参数 + * @returns 功能定义数据传输对象 + */ + function updateApi( + name: string, + input: FeatureGroupDefinitionUpdateDto, + ): Promise { + return request( + `/api/feature-management/definitions/groups/${name}`, + { + data: input, + method: 'PUT', + }, + ); + } + + return { + cancel, + createApi, + deleteApi, + getApi, + getListApi, + updateApi, + }; +} diff --git a/apps/vben5/packages/@abp/features/src/api/useFeaturesApi.ts b/apps/vben5/packages/@abp/features/src/api/useFeaturesApi.ts new file mode 100644 index 000000000..116817733 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/api/useFeaturesApi.ts @@ -0,0 +1,62 @@ +import type { + FeatureProvider, + GetFeatureListResultDto, + UpdateFeaturesDto, +} from '../types/features'; + +import { useRequest } from '@abp/request'; + +export function useFeaturesApi() { + const { cancel, request } = useRequest(); + + /** + * 删除功能 + * @param {FeatureProvider} provider 参数 + * @returns {Promise} + */ + function deleteApi(provider: FeatureProvider): Promise { + return request(`/api/feature-management/features`, { + method: 'DELETE', + params: provider, + }); + } + + /** + * 查询功能 + * @param {FeatureProvider} provider 参数 + * @returns {Promise} 功能实体数据传输对象 + */ + function getApi(provider: FeatureProvider): Promise { + return request( + `/api/feature-management/features`, + { + method: 'GET', + params: provider, + }, + ); + } + + /** + * 更新功能 + * @param {FeatureProvider} provider + * @param {UpdateFeaturesDto} input 参数 + * @returns {Promise} + */ + function updateApi( + provider: FeatureProvider, + input: UpdateFeaturesDto, + ): Promise { + return request(`/api/feature-management/features`, { + data: input, + method: 'PUT', + params: provider, + }); + } + + return { + cancel, + deleteApi, + getApi, + updateApi, + }; +} diff --git a/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionModal.vue b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionModal.vue new file mode 100644 index 000000000..4b812dfa3 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionModal.vue @@ -0,0 +1,467 @@ + + + + + + + + + + onGroupChange(e?.toString())" + /> + + + + + + + + + + + + + + + + + + + (formModel.defaultValue = String( + e.target.checked, + ).toLowerCase()) + " + > + {{ $t('AbpFeatureManagement.DisplayName:DefaultValue') }} + + + + + {{ $t('AbpFeatureManagement.DisplayName:IsVisibleToClients') }} + + + + + {{ $t('AbpFeatureManagement.DisplayName:IsAvailableToHost') }} + + + + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue new file mode 100644 index 000000000..2ad85a924 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/definitions/features/FeatureDefinitionTable.vue @@ -0,0 +1,346 @@ + + + + + + + {{ $t('AbpFeatureManagement.FeatureDefinitions:AddNew') }} + + + + + + + + + + + + + + + + + + + + {{ $t('AbpUi.Edit') }} + + + {{ $t('AbpUi.Delete') }} + + + + + + + onGet()" /> + + + diff --git a/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionModal.vue b/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionModal.vue new file mode 100644 index 000000000..5c2d49b13 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionModal.vue @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue b/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue new file mode 100644 index 000000000..4534a8fd7 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/definitions/groups/FeatureGroupDefinitionTable.vue @@ -0,0 +1,261 @@ + + + + + + + {{ $t('AbpFeatureManagement.GroupDefinitions:AddNew') }} + + + + + + {{ $t('AbpUi.Edit') }} + + + {{ $t('AbpUi.Delete') }} + + + + onMenuClick(row, info)"> + + {{ $t('AbpFeatureManagement.GroupDefinitions:AddNew') }} + + + + + + + + + onGet()" /> + + + + diff --git a/apps/vben5/packages/@abp/features/src/components/features/FeatureModal.vue b/apps/vben5/packages/@abp/features/src/components/features/FeatureModal.vue new file mode 100644 index 000000000..cc8f0bd06 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/features/FeatureModal.vue @@ -0,0 +1,262 @@ + + + + + + + + + + + {{ feature.displayName }} + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/features/src/components/index.ts b/apps/vben5/packages/@abp/features/src/components/index.ts new file mode 100644 index 000000000..32ec4e334 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/components/index.ts @@ -0,0 +1,3 @@ +export { default as FeatureDefinitionTable } from './definitions/features/FeatureDefinitionTable.vue'; +export { default as FeatureGroupDefinitionTable } from './definitions/groups/FeatureGroupDefinitionTable.vue'; +export { default as FeatureModal } from './features/FeatureModal.vue'; diff --git a/apps/vben5/packages/@abp/features/src/constants/index.ts b/apps/vben5/packages/@abp/features/src/constants/index.ts new file mode 100644 index 000000000..c85954d3e --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/constants/index.ts @@ -0,0 +1 @@ +export * from './permissions'; diff --git a/apps/vben5/packages/@abp/features/src/constants/permissions.ts b/apps/vben5/packages/@abp/features/src/constants/permissions.ts new file mode 100644 index 000000000..041eb2c9a --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/constants/permissions.ts @@ -0,0 +1,20 @@ +/** 分组权限 */ +export const GroupDefinitionsPermissions = { + /** 新增 */ + Create: 'FeatureManagement.GroupDefinitions.Create', + Default: 'FeatureManagement.GroupDefinitions', + /** 删除 */ + Delete: 'FeatureManagement.GroupDefinitions.Delete', + /** 更新 */ + Update: 'FeatureManagement.GroupDefinitions.Update', +}; +/** 功能定义权限 */ +export const FeatureDefinitionsPermissions = { + /** 新增 */ + Create: 'FeatureManagement.Definitions.Create', + Default: 'FeatureManagement.Definitions', + /** 删除 */ + Delete: 'FeatureManagement.Definitions.Delete', + /** 更新 */ + Update: 'FeatureManagement.Definitions.Update', +}; diff --git a/apps/vben5/packages/@abp/features/src/index.ts b/apps/vben5/packages/@abp/features/src/index.ts new file mode 100644 index 000000000..f43dbaee0 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/index.ts @@ -0,0 +1,4 @@ +export * from './api'; +export * from './components'; +export * from './constants'; +export * from './types'; diff --git a/apps/vben5/packages/@abp/features/src/types/definitions.ts b/apps/vben5/packages/@abp/features/src/types/definitions.ts new file mode 100644 index 000000000..701f5fa82 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/types/definitions.ts @@ -0,0 +1,47 @@ +import type { IHasConcurrencyStamp, IHasExtraProperties } from '@abp/core'; + +interface FeatureDefinitionDto extends IHasExtraProperties { + allowedProviders: string[]; + defaultValue?: string; + description?: string; + displayName: string; + groupName: string; + isAvailableToHost: boolean; + isStatic: boolean; + isVisibleToClients: boolean; + name: string; + parentName?: string; + valueType: string; +} + +interface FeatureDefinitionGetListInput { + filter?: string; + groupName?: string; +} + +interface FeatureDefinitionCreateOrUpdateDto extends IHasExtraProperties { + allowedProviders: string[]; + defaultValue?: string; + description?: string; + displayName: string; + isAvailableToHost: boolean; + isVisibleToClients: boolean; + parentName?: string; + valueType: string; +} + +interface FeatureDefinitionUpdateDto + extends FeatureDefinitionCreateOrUpdateDto, + IHasConcurrencyStamp {} +interface FeatureDefinitionCreateDto + extends FeatureDefinitionCreateOrUpdateDto { + groupName: string; + name: string; +} + +export type { + FeatureDefinitionCreateDto, + FeatureDefinitionDto, + FeatureDefinitionGetListInput, + FeatureDefinitionUpdateDto, +}; diff --git a/apps/vben5/packages/@abp/features/src/types/features.ts b/apps/vben5/packages/@abp/features/src/types/features.ts new file mode 100644 index 000000000..930c53957 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/types/features.ts @@ -0,0 +1,63 @@ +import type { Dictionary, NameValue } from '@abp/core'; + +interface FeatureProvider { + providerKey?: string; + providerName: string; +} + +interface IValueValidator { + [key: string]: any; + isValid(value?: any): boolean; + name: string; + properties: Dictionary; +} + +interface IStringValueType { + [key: string]: any; + name: string; + properties: Dictionary; + validator: IValueValidator; +} + +interface FeatureProviderDto { + key: string; + name: string; +} + +interface FeatureDto { + depth: number; + description?: string; + displayName: string; + name: string; + parentName?: string; + provider: FeatureProviderDto; + value?: any; + valueType: IStringValueType; +} + +interface FeatureGroupDto { + displayName: string; + features: FeatureDto[]; + name: string; +} + +interface GetFeatureListResultDto { + groups: FeatureGroupDto[]; +} + +type UpdateFeatureDto = NameValue; + +interface UpdateFeaturesDto { + features: UpdateFeatureDto[]; +} + +export type { + FeatureDto, + FeatureGroupDto, + FeatureProvider, + GetFeatureListResultDto, + IStringValueType, + IValueValidator, + UpdateFeatureDto, + UpdateFeaturesDto, +}; diff --git a/apps/vben5/packages/@abp/features/src/types/groups.ts b/apps/vben5/packages/@abp/features/src/types/groups.ts new file mode 100644 index 000000000..35b93da30 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/types/groups.ts @@ -0,0 +1,31 @@ +import type { IHasConcurrencyStamp, IHasExtraProperties } from '@abp/core'; + +interface FeatureGroupDefinitionDto extends IHasExtraProperties { + displayName: string; + isStatic: boolean; + name: string; +} + +interface FeatureGroupDefinitionGetListInput { + filter?: string; +} + +interface FeatureGroupDefinitionCreateOrUpdateDto extends IHasExtraProperties { + displayName: string; +} + +interface FeatureGroupDefinitionUpdateDto + extends FeatureGroupDefinitionCreateOrUpdateDto, + IHasConcurrencyStamp {} + +interface FeatureGroupDefinitionCreateDto + extends FeatureGroupDefinitionCreateOrUpdateDto { + name: string; +} + +export type { + FeatureGroupDefinitionCreateDto, + FeatureGroupDefinitionDto, + FeatureGroupDefinitionGetListInput, + FeatureGroupDefinitionUpdateDto, +}; diff --git a/apps/vben5/packages/@abp/features/src/types/index.ts b/apps/vben5/packages/@abp/features/src/types/index.ts new file mode 100644 index 000000000..4bdb3f8f7 --- /dev/null +++ b/apps/vben5/packages/@abp/features/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './definitions'; +export * from './features'; +export * from './groups'; diff --git a/apps/vben5/packages/@abp/features/tsconfig.json b/apps/vben5/packages/@abp/features/tsconfig.json new file mode 100644 index 000000000..ce1a891fb --- /dev/null +++ b/apps/vben5/packages/@abp/features/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/web.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/apps/vben5/packages/@abp/gdpr/src/api/index.ts b/apps/vben5/packages/@abp/gdpr/src/api/index.ts index da43240af..8b5bb48d4 100644 --- a/apps/vben5/packages/@abp/gdpr/src/api/index.ts +++ b/apps/vben5/packages/@abp/gdpr/src/api/index.ts @@ -1 +1 @@ -export * from './useGdprRequestsApi'; +export { useGdprRequestsApi } from './useGdprRequestsApi'; diff --git a/apps/vben5/packages/@abp/platform/src/api/index.ts b/apps/vben5/packages/@abp/platform/src/api/index.ts index 11e2df3ff..340455006 100644 --- a/apps/vben5/packages/@abp/platform/src/api/index.ts +++ b/apps/vben5/packages/@abp/platform/src/api/index.ts @@ -1,2 +1,2 @@ -export * from './useEmailMessagesApi'; -export * from './useSmsMessagesApi'; +export { useEmailMessagesApi } from './useEmailMessagesApi'; +export { useSmsMessagesApi } from './useSmsMessagesApi'; diff --git a/apps/vben5/packages/@abp/saas/package.json b/apps/vben5/packages/@abp/saas/package.json index 1f6815328..d8fe93258 100644 --- a/apps/vben5/packages/@abp/saas/package.json +++ b/apps/vben5/packages/@abp/saas/package.json @@ -22,6 +22,7 @@ "dependencies": { "@abp/auditing": "workspace:*", "@abp/core": "workspace:*", + "@abp/features": "workspace:*", "@abp/request": "workspace:*", "@abp/ui": "workspace:*", "@ant-design/icons-vue": "catalog:", diff --git a/apps/vben5/packages/@abp/saas/src/api/index.ts b/apps/vben5/packages/@abp/saas/src/api/index.ts index 3f43296a6..beba3fb43 100644 --- a/apps/vben5/packages/@abp/saas/src/api/index.ts +++ b/apps/vben5/packages/@abp/saas/src/api/index.ts @@ -1,2 +1,2 @@ -export * from './useEditionsApi'; -export * from './useTenantsApi'; +export { useEditionsApi } from './useEditionsApi'; +export { useTenantsApi } from './useTenantsApi'; diff --git a/apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue b/apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue index 67174332f..57689e369 100644 --- a/apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue +++ b/apps/vben5/packages/@abp/saas/src/components/tenants/TenantModal.vue @@ -214,6 +214,13 @@ onMounted(onSearchEditions); {{ $t('AbpSaas.DisplayName:IsActive') }} + + onNameChange(e.target.value)" + autocomplete="off" + /> + - - onNameChange(e.target.value)" - autocomplete="off" - /> - { entityChangeDrawerApi.open(); break; } + case 'features': { + tenantFeatureModalApi.setData({ + displayName: row.name, + providerKey: row.id, + providerName: 'T', + }); + tenantFeatureModalApi.open(); + } } }; @@ -233,6 +246,13 @@ const onMenuClick = (row: TenantDto, info: MenuInfo) => { > {{ $t('AbpSaas.ConnectionStrings') }} + + {{ $t('AbpSaas.ManageFeatures') }} + query()" /> + diff --git a/apps/vben5/packages/@abp/settings/package.json b/apps/vben5/packages/@abp/settings/package.json index 0f8f957ac..1e2c40f78 100644 --- a/apps/vben5/packages/@abp/settings/package.json +++ b/apps/vben5/packages/@abp/settings/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@abp/core": "workspace:*", + "@abp/features": "workspace:*", "@abp/request": "workspace:*", "@abp/ui": "workspace:*", "@ant-design/icons-vue": "catalog:", diff --git a/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue b/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue index 59b7b7fc9..e7b0a6104 100644 --- a/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue +++ b/apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue @@ -110,16 +110,19 @@ onMounted(onGet); - - {{ $t('AbpUi.Submit') }} - + + + + {{ $t('AbpUi.Submit') }} + + diff --git a/apps/vben5/packages/@abp/settings/src/components/settings/SystemSetting.vue b/apps/vben5/packages/@abp/settings/src/components/settings/SystemSetting.vue index 4c8621385..0d1dd76cb 100644 --- a/apps/vben5/packages/@abp/settings/src/components/settings/SystemSetting.vue +++ b/apps/vben5/packages/@abp/settings/src/components/settings/SystemSetting.vue @@ -3,9 +3,11 @@ import type { SettingsUpdateInput } from '../../types'; import { ref } from 'vue'; +import { useVbenModal } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { isEmail, useAbpStore } from '@abp/core'; +import { FeatureModal } from '@abp/features'; import { Button, Form, InputSearch, message, Modal } from 'ant-design-vue'; import { @@ -24,6 +26,9 @@ defineOptions({ const FormItem = Form.Item; const abpStore = useAbpStore(); +const [HostFeatureModal, featureModalApi] = useVbenModal({ + connectedComponent: FeatureModal, +}); const sending = ref(false); @@ -61,10 +66,27 @@ async function onSendMail(email: string) { sending.value = false; } } + +function onFeatureManage() { + featureModalApi.setData({ + providerName: 'T', + }); + featureModalApi.open(); +} + + + {{ $t('AbpFeatureManagement.ManageHostFeatures') }} + + + diff --git a/apps/vben5/packages/@abp/ui/src/components/index.ts b/apps/vben5/packages/@abp/ui/src/components/index.ts index 49f73dbfd..3e9b28b37 100644 --- a/apps/vben5/packages/@abp/ui/src/components/index.ts +++ b/apps/vben5/packages/@abp/ui/src/components/index.ts @@ -1,4 +1,5 @@ export { default as LocalizableInput } from './localizable-input/LocalizableInput.vue'; export { default as PropertyTable } from './properties/PropertyTable.vue'; export * from './properties/types'; +export * from './string-value-type'; export type * from './vxe-table'; diff --git a/apps/vben5/packages/@abp/ui/src/components/string-value-type/StringValueTypeInput.vue b/apps/vben5/packages/@abp/ui/src/components/string-value-type/StringValueTypeInput.vue new file mode 100644 index 000000000..cf0be6c26 --- /dev/null +++ b/apps/vben5/packages/@abp/ui/src/components/string-value-type/StringValueTypeInput.vue @@ -0,0 +1,765 @@ + + + + + + + + + + + + {{ $t('component.value_type_nput.type.name') }} + + + + + + {{ + $t('component.value_type_nput.validator.name') + }} + + + + + handleValueTypeChange(value?.toString())" + > + + {{ $t('component.value_type_nput.type.FREE_TEXT.name') }} + + + {{ $t('component.value_type_nput.type.TOGGLE.name') }} + + + {{ $t('component.value_type_nput.type.SELECTION.name') }} + + + + + + + + handleValidatorChange(value?.toString())" + > + + {{ $t('component.value_type_nput.validator.NULL.name') }} + + + {{ $t('component.value_type_nput.validator.BOOLEAN.name') }} + + + {{ $t('component.value_type_nput.validator.NUMERIC.name') }} + + + {{ $t('component.value_type_nput.validator.STRING.name') }} + + + + + + + + + + + + + + {{ + $t( + 'component.value_type_nput.validator.NUMERIC.minValue', + ) + }} + + + + + + {{ + $t( + 'component.value_type_nput.validator.NUMERIC.maxValue', + ) + }} + + + + + + + + + + + + + + + + + + + {{ + $t( + 'component.value_type_nput.validator.STRING.allowNull', + ) + }} + + + + + + {{ + $t( + 'component.value_type_nput.validator.STRING.regularExpression', + ) + }} + + + + + + + + + + {{ + $t( + 'component.value_type_nput.validator.STRING.minLength', + ) + }} + + + + + + {{ + $t( + 'component.value_type_nput.validator.STRING.maxLength', + ) + }} + + + + + + + + + + + + + + + + + + + + + + {{ + $t( + 'component.value_type_nput.type.SELECTION.itemsNotBeEmpty', + ) + }} + + + + + + {{ + $t( + 'component.value_type_nput.type.SELECTION.actions.create', + ) + }} + + + {{ + $t( + 'component.value_type_nput.type.SELECTION.actions.clean', + ) + }} + + + + + + + + + {{ getDisplayName(record.displayText) }} + + + + handleEdit(record)" + > + + + + {{ + $t( + 'component.value_type_nput.type.SELECTION.actions.update', + ) + }} + + handleDelete(record)" + danger + > + + + + {{ + $t( + 'component.value_type_nput.type.SELECTION.actions.delete', + ) + }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/vben5/packages/@abp/ui/src/components/string-value-type/index.ts b/apps/vben5/packages/@abp/ui/src/components/string-value-type/index.ts new file mode 100644 index 000000000..7560b8843 --- /dev/null +++ b/apps/vben5/packages/@abp/ui/src/components/string-value-type/index.ts @@ -0,0 +1,4 @@ +export * from './interface'; +export { default as StringValueTypeInput } from './StringValueTypeInput.vue'; +export * from './validator'; +export * from './valueType'; diff --git a/apps/vben5/packages/@abp/ui/src/components/string-value-type/interface.ts b/apps/vben5/packages/@abp/ui/src/components/string-value-type/interface.ts new file mode 100644 index 000000000..b28d11292 --- /dev/null +++ b/apps/vben5/packages/@abp/ui/src/components/string-value-type/interface.ts @@ -0,0 +1,3 @@ +export interface StringValueTypeInstance { + validate(value: any): Promise; +} diff --git a/apps/vben5/packages/@abp/ui/src/components/string-value-type/validator.ts b/apps/vben5/packages/@abp/ui/src/components/string-value-type/validator.ts new file mode 100644 index 000000000..e97848cc9 --- /dev/null +++ b/apps/vben5/packages/@abp/ui/src/components/string-value-type/validator.ts @@ -0,0 +1,143 @@ +import type { Dictionary } from '@abp/core'; + +import { isBoolean, isNumber } from '@vben/utils'; + +import { isNullOrUnDef, isNullOrWhiteSpace } from '@abp/core'; + +export interface ValueValidator { + isValid(value?: any): boolean; + name: string; + + properties: Dictionary; +} + +export class AlwaysValidValueValidator implements ValueValidator { + name = 'NULL'; + properties: Dictionary; + constructor() { + this.properties = {}; + } + isValid(_value?: any): boolean { + return true; + } +} + +export class BooleanValueValidator implements ValueValidator { + name = 'BOOLEAN'; + properties: Dictionary; + 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; + get maxValue(): number | undefined { + return Number(this.properties.MaxValue); + } + + set maxValue(value: number) { + this.properties.MaxValue = value; + } + + get minValue(): number | undefined { + return Number(this.properties.MinValue); + } + + set minValue(value: number) { + this.properties.MinValue = value; + } + + constructor() { + this.properties = {}; + } + + _isValidInternal(value: number): boolean { + if (this.minValue && value < this.minValue) return false; + if (this.maxValue && value > this.maxValue) return false; + return true; + } + + 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; + } +} + +export class StringValueValidator implements ValueValidator { + name = 'STRING'; + properties: Dictionary; + get allowNull(): boolean { + return ( + String(this.properties.AllowNull ?? 'true')?.toLowerCase() === 'true' + ); + } + + set allowNull(value: boolean) { + this.properties.AllowNull = value; + } + + get maxLength(): number | undefined { + return Number(this.properties.MaxLength); + } + + set maxLength(value: number) { + this.properties.MaxLength = value; + } + + get minLength(): number | undefined { + return Number(this.properties.MinLength); + } + + set minLength(value: number) { + this.properties.MinLength = value; + } + + get regularExpression(): string { + return String(this.properties.RegularExpression ?? ''); + } + + set regularExpression(value: string) { + this.properties.RegularExpression = value; + } + + constructor() { + this.properties = {}; + } + + 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 new RegExp(this.regularExpression).test(valueString); + } + return true; + } +} diff --git a/apps/vben5/packages/@abp/ui/src/components/string-value-type/valueType.ts b/apps/vben5/packages/@abp/ui/src/components/string-value-type/valueType.ts new file mode 100644 index 000000000..1d4cdab5e --- /dev/null +++ b/apps/vben5/packages/@abp/ui/src/components/string-value-type/valueType.ts @@ -0,0 +1,120 @@ +import type { Dictionary, LocalizableStringInfo } from '@abp/core'; + +import type { ValueValidator } from './validator'; + +import { + AlwaysValidValueValidator, + BooleanValueValidator, + NumericValueValidator, + StringValueValidator, +} from './validator'; + +export interface StringValueType { + name: string; + properties: Dictionary; + validator: ValueValidator; +} + +export interface SelectionStringValueItem { + displayText: LocalizableStringInfo; + value: string; +} + +export interface SelectionStringValueItemSource { + items: SelectionStringValueItem[]; +} + +export class FreeTextStringValueType implements StringValueType { + name = 'FreeTextStringValueType'; + properties: Dictionary; + validator: ValueValidator; + constructor(validator?: ValueValidator) { + this.properties = {}; + this.validator = validator ?? new AlwaysValidValueValidator(); + } +} + +export class ToggleStringValueType implements StringValueType { + name = 'ToggleStringValueType'; + properties: Dictionary; + validator: ValueValidator; + constructor(validator?: ValueValidator) { + this.properties = {}; + this.validator = validator ?? new BooleanValueValidator(); + } +} + +export class SelectionStringValueType implements StringValueType { + itemSource: SelectionStringValueItemSource; + name = 'SelectionStringValueType'; + properties: Dictionary; + validator: ValueValidator; + constructor(validator?: ValueValidator) { + this.properties = {}; + this.itemSource = { + items: [], + }; + this.validator = validator ?? new AlwaysValidValueValidator(); + } +} + +class StringValueTypeSerializer { + _deserializeValidator(validator: any): ValueValidator { + let convertValidator: ValueValidator = new AlwaysValidValueValidator(); + if (validator.name) { + switch (validator.name) { + case 'BOOLEAN': { + convertValidator = new BooleanValueValidator(); + break; + } + case 'NULL': { + convertValidator = new AlwaysValidValueValidator(); + break; + } + case 'NUMERIC': { + convertValidator = new NumericValueValidator(); + break; + } + case 'STRING': { + convertValidator = new StringValueValidator(); + break; + } + } + } + convertValidator.properties = validator.properties; + return convertValidator; + } + + deserialize(value: string): StringValueType { + let valueType: StringValueType; + const valueTypeObj = JSON.parse(value); + switch (valueTypeObj.name) { + case 'SELECTION': + case 'SelectionStringValueType': { + valueType = new SelectionStringValueType(); + (valueType as SelectionStringValueType).itemSource = + valueTypeObj.itemSource; + break; + } + case 'TOGGLE': + case 'ToggleStringValueType': { + valueType = new ToggleStringValueType(); + break; + } + default: { + valueType = new FreeTextStringValueType(); + break; + } + } + valueType.properties = valueTypeObj.properties; + valueType.validator = this._deserializeValidator(valueTypeObj.validator); + return valueType; + } + + serialize(value: StringValueType): string { + const valueTypeString = JSON.stringify(value); + return valueTypeString; + } +} + +export const valueTypeSerializer = new StringValueTypeSerializer();