3977 changed files with 81903 additions and 88085 deletions
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
@ -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, |
|
||||
}); |
|
||||
}; |
|
||||
@ -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[]; |
|
||||
} |
|
||||
@ -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}`, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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, |
||||
|
}); |
||||
|
} |
||||
@ -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, |
|
||||
}); |
|
||||
} |
|
||||
@ -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}`, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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}`, |
||||
|
}); |
||||
|
}; |
||||
@ -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; |
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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, |
|
||||
}, |
|
||||
}); |
|
||||
} |
|
||||
@ -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, |
||||
|
}); |
||||
|
}; |
||||
@ -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[]; |
||||
|
} |
||||
@ -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', |
|
||||
}); |
|
||||
}; |
|
||||
@ -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`, |
||||
|
}); |
||||
|
}; |
||||
@ -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[]; |
||||
|
} |
||||
@ -0,0 +1,271 @@ |
|||||
|
<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 v-bind="state.table"> |
||||
|
<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 { TableProps } from 'ant-design-vue'; |
||||
|
import type { RuleObject } from 'ant-design-vue/lib/form'; |
||||
|
import { cloneDeep } from 'lodash-es'; |
||||
|
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'; |
||||
|
import { nextTick, reactive, ref, unref, watch } from '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 State { |
||||
|
editFlag: boolean, |
||||
|
table: TableProps, |
||||
|
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>, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
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 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: { |
||||
|
sticky: true, |
||||
|
rowKey: "key", |
||||
|
columns: [ |
||||
|
{ |
||||
|
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', |
||||
|
} |
||||
|
], |
||||
|
dataSource: [], |
||||
|
scroll: { |
||||
|
x: 1500, |
||||
|
}, |
||||
|
} |
||||
|
}); |
||||
|
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, |
||||
|
} |
||||
|
); |
||||
|
watch( |
||||
|
() => [props.allowEdit, props.allowDelete], |
||||
|
([allowEdit, allowDelete]) => { |
||||
|
if (allowEdit || allowDelete) { |
||||
|
nextTick(() => { |
||||
|
state.table.columns!.push({ |
||||
|
width: 220, |
||||
|
title: t('component.extra_property_dictionary.actions.title'), |
||||
|
align: 'center', |
||||
|
dataIndex: 'action', |
||||
|
key: 'action', |
||||
|
fixed: 'right', |
||||
|
}); |
||||
|
}); |
||||
|
} else { |
||||
|
nextTick(() => { |
||||
|
const columns = state.table.columns ?? []; |
||||
|
const findIndex = columns.findIndex(x => x.key === 'action'); |
||||
|
columns.splice(findIndex, 1); |
||||
|
state.table.columns = columns; |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
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> |
||||
@ -0,0 +1,3 @@ |
|||||
|
import ExtraPropertyDictionary from './ExtraPropertyDictionary.vue'; |
||||
|
|
||||
|
export { ExtraPropertyDictionary }; |
||||
@ -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,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> |
||||
@ -0,0 +1,168 @@ |
|||||
|
<template> |
||||
|
<div style="width: 100%"> |
||||
|
<ItemReset> |
||||
|
<InputGroup> |
||||
|
<Row :gutter="4" style="margin-top: 10px;"> |
||||
|
<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="handleResourceChange" |
||||
|
/> |
||||
|
</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; |
||||
|
} |
||||
|
if (state.displayName !== info.name) { |
||||
|
state.displayName = isNullOrWhiteSpace(info.name) ? undefined : info.name; |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true, |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
function handleResourceChange(value?: string) { |
||||
|
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; |
||||
|
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> |
||||
@ -0,0 +1,3 @@ |
|||||
|
import LocalizableInput from './LocalizableInput.vue'; |
||||
|
|
||||
|
export { LocalizableInput }; |
||||
@ -0,0 +1,9 @@ |
|||||
|
export interface Props { |
||||
|
resources: string[], |
||||
|
value: string, |
||||
|
} |
||||
|
|
||||
|
export interface Resource { |
||||
|
name: string, |
||||
|
value: string, |
||||
|
} |
||||
@ -0,0 +1,551 @@ |
|||||
|
<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 :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 :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 |
||||
|
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 |
||||
|
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 |
||||
|
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 |
||||
|
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 |
||||
|
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 |
||||
|
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="tableColumns" :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="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 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 { 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 tableColumns = reactive<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, |
||||
|
}, |
||||
|
{ |
||||
|
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> |
||||
@ -0,0 +1,3 @@ |
|||||
|
import StringValueTypeInput from './StringValueTypeInput.vue'; |
||||
|
|
||||
|
export { StringValueTypeInput }; |
||||
@ -0,0 +1,134 @@ |
|||||
|
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 (value === undefined) 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 (!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 { |
||||
|
console.log(value); |
||||
|
console.log(this.allowNull); |
||||
|
if (!this.allowNull && isNullOrUnDef(value)) return false; |
||||
|
const valueString = String(value); |
||||
|
console.log(valueString); |
||||
|
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false; |
||||
|
console.log(valueString.length); |
||||
|
console.log(this.maxLength); |
||||
|
console.log(this.maxLength); |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
@ -0,0 +1,4 @@ |
|||||
|
export * from './ExtraPropertyDictionary'; |
||||
|
export * from './FeatureModal'; |
||||
|
export * from './LocalizableInput'; |
||||
|
export * from './StringValueType'; |
||||
@ -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, |
||||
|
} |
||||
|
} |
||||
@ -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])); |
||||
|
} |
||||
@ -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: 'blur', |
||||
|
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> |
||||
@ -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> |
||||
@ -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 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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: 350, |
||||
|
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:IsStatic'), |
||||
|
dataIndex: 'isStatic', |
||||
|
align: 'center', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
defaultHidden: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isStatic'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -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> |
||||
@ -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, |
|
||||
}; |
|
||||
} |
|
||||
@ -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> |
|
||||
@ -0,0 +1,230 @@ |
|||||
|
<template> |
||||
|
<BasicModal |
||||
|
@register="registerModal" |
||||
|
:title="L('Settings')" |
||||
|
: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="defaultValue" :label="L('DisplayName:DefaultValue')"> |
||||
|
<TextArea |
||||
|
:disabled="state.entityEditFlag && !state.allowedChange" |
||||
|
:allow-clear="true" |
||||
|
:auto-size="{ minRows: 3 }" |
||||
|
v-model:value="state.entity.defaultValue" |
||||
|
/> |
||||
|
</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="providers" :label="L('DisplayName:Providers')" :extra="L('Description:Providers')"> |
||||
|
<Select mode="multiple" :disabled="!state.allowedChange" :allow-clear="true" v-model:value="state.entity.providers" :options="providers" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="isInherited" :label="L('DisplayName:IsInherited')" :extra="L('Description:IsInherited')"> |
||||
|
<Checkbox |
||||
|
:disabled="!state.allowedChange" |
||||
|
v-model:checked="state.entity.isInherited" |
||||
|
>{{ L('DisplayName:IsInherited') }} |
||||
|
</Checkbox> |
||||
|
</FormItem> |
||||
|
<FormItem name="isEncrypted" :label="L('DisplayName:IsEncrypted')" :extra="L('Description:IsEncrypted')"> |
||||
|
<Checkbox |
||||
|
:disabled="!state.allowedChange" |
||||
|
v-model:checked="state.entity.isEncrypted" |
||||
|
>{{ L('DisplayName:IsEncrypted') }} |
||||
|
</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> |
||||
|
</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 { cloneDeep } from 'lodash-es'; |
||||
|
import { ref, reactive, unref, nextTick, watch } from 'vue'; |
||||
|
import { Checkbox, Form, Input, Select, Tabs } from 'ant-design-vue'; |
||||
|
import { BasicModal, useModalInner } from '/@/components/Modal'; |
||||
|
import { LocalizableInput, ExtraPropertyDictionary } from '/@/components/Abp'; |
||||
|
import { ModalState } from '../types/props'; |
||||
|
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/settings-management/definitions'; |
||||
|
import { SettingDefinitionDto, SettingDefinitionUpdateDto, SettingDefinitionCreateDto } from '/@/api/settings-management/definitions/model'; |
||||
|
|
||||
|
const FormItem = Form.Item; |
||||
|
const TabPane = Tabs.TabPane; |
||||
|
const TextArea = Input.TextArea; |
||||
|
|
||||
|
const emits = defineEmits(['register', 'change']); |
||||
|
|
||||
|
const { ruleCreator } = useValidation(); |
||||
|
const { validate } = useLocalizationSerializer(); |
||||
|
const { createConfirm, createMessage } = useMessage(); |
||||
|
const { L } = useLocalization(['AbpSettingManagement', 'AbpUi']); |
||||
|
|
||||
|
const formRef = ref<any>(); |
||||
|
const providers = reactive([ |
||||
|
{ label: L('Providers:Default'), value: 'D' }, |
||||
|
{ label: L('Providers:Configuration'), value: 'C' }, |
||||
|
{ label: L('Providers:Global'), value: 'G' }, |
||||
|
{ label: L('Providers:Tenant'), value: 'T' }, |
||||
|
{ label: L('Providers:User'), value: 'U' }, |
||||
|
]); |
||||
|
const state = reactive<ModalState>({ |
||||
|
activeTab: 'basic', |
||||
|
entity: { |
||||
|
isInherited: true, |
||||
|
} as SettingDefinitionDto, |
||||
|
allowedChange: false, |
||||
|
entityChanged: false, |
||||
|
entityEditFlag: false, |
||||
|
entityRules: { |
||||
|
name: ruleCreator.fieldRequired({ |
||||
|
name: 'Name', |
||||
|
prefix: 'DisplayName', |
||||
|
resourceName: 'AbpSettingManagement', |
||||
|
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 = { |
||||
|
isInherited: true, |
||||
|
}; |
||||
|
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 SettingDefinitionUpdateDto) |
||||
|
: CreateAsyncByInput(cloneDeep(state.entity) as SettingDefinitionCreateDto); |
||||
|
api.then((res) => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
emits('change', res); |
||||
|
form.resetFields(); |
||||
|
closeModal(); |
||||
|
}).finally(() => { |
||||
|
changeOkLoading(false); |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,155 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #toolbar> |
||||
|
<Button |
||||
|
v-auth="['SettingManagement.Definition.Create']" |
||||
|
type="primary" |
||||
|
@click="handleAddNew" |
||||
|
> |
||||
|
{{ L('Definition: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 === '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 === 'isInherited'"> |
||||
|
<CheckOutlined v-if="record.isInherited" class="enable" /> |
||||
|
<CloseOutlined v-else class="disable" /> |
||||
|
</template> |
||||
|
<template v-else-if="column.key === 'isEncrypted'"> |
||||
|
<CheckOutlined v-if="record.isEncrypted" 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 === 'providers'"> |
||||
|
<Tag v-for="provider in record.providers" style="margin-left: 5px" color="blue">{{ provider }}</Tag> |
||||
|
</template> |
||||
|
<template v-else-if="column.key === 'action'"> |
||||
|
<TableAction |
||||
|
:stop-button-propagation="true" |
||||
|
:actions="[ |
||||
|
{ |
||||
|
auth: 'SettingManagement.Definition.Update', |
||||
|
label: L('Edit'), |
||||
|
icon: 'ant-design:edit-outlined', |
||||
|
onClick: handleEdit.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'SettingManagement.Definition.DeleteOrRestore', |
||||
|
label: L('DeleteOrRestore'), |
||||
|
color: 'error', |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
ifShow: !record.isStatic, |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<SettingDefinitionModal @register="registerModal" @change="fetch" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, onMounted } from 'vue'; |
||||
|
import { Button, Tag } 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, DeleteOrRestoreAsyncByName } from '/@/api/settings-management/definitions'; |
||||
|
import { getSearchFormSchemas } from '../datas/ModalData'; |
||||
|
import SettingDefinitionModal from './SettingDefinitionModal.vue'; |
||||
|
|
||||
|
const { deserialize } = useLocalizationSerializer(); |
||||
|
const { L, Lr } = useLocalization(['AbpSettingManagement', 'AbpUi']); |
||||
|
const { createConfirm, createMessage } = useMessage(); |
||||
|
const [registerModal, { openModal }] = useModal(); |
||||
|
const [registerTable, { setLoading, getForm, setTableData }] = useTable({ |
||||
|
rowKey: 'name', |
||||
|
title: L('Settings'), |
||||
|
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: 200, |
||||
|
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); |
||||
|
setTableData([]); |
||||
|
var input = form.getFieldsValue(); |
||||
|
GetListAsyncByInput(input).then((res) => { |
||||
|
setTableData(res.items); |
||||
|
}).finally(() => { |
||||
|
setLoading(false); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleAddNew() { |
||||
|
openModal(true, {}); |
||||
|
} |
||||
|
|
||||
|
function handleEdit(record) { |
||||
|
openModal(true, record); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
createConfirm({ |
||||
|
iconType: 'warning', |
||||
|
title: L('AreYouSure'), |
||||
|
content: L('ItemWillBeDeleteOrRestoreMessage'), |
||||
|
onOk: () => { |
||||
|
return DeleteOrRestoreAsyncByName(record.name).then(() => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
fetch(); |
||||
|
}); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,30 @@ |
|||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { FormSchema } from '/@/components/Form'; |
||||
|
|
||||
|
const { L } = useLocalization(['AbpSettingManagement', 'AbpUi']); |
||||
|
|
||||
|
export function getSearchFormSchemas():FormSchema[] { |
||||
|
return [ |
||||
|
{ |
||||
|
field: 'providerName', |
||||
|
component: 'Select', |
||||
|
label: L('DisplayName:Providers'), |
||||
|
colProps: { span: 6 }, |
||||
|
componentProps: { |
||||
|
options: [ |
||||
|
{ label: L('Providers:Default'), value: 'D' }, |
||||
|
{ label: L('Providers:Configuration'), value: 'C' }, |
||||
|
{ label: L('Providers:Global'), value: 'G' }, |
||||
|
{ label: L('Providers:Tenant'), value: 'T' }, |
||||
|
{ label: L('Providers:User'), value: 'U' }, |
||||
|
], |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
field: 'filter', |
||||
|
component: 'Input', |
||||
|
label: L('Search'), |
||||
|
colProps: { span: 18 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { BasicColumn } from '/@/components/Table'; |
||||
|
import { sorter } from '/@/utils/table'; |
||||
|
|
||||
|
const { L } = useLocalization(['AbpSettingManagement']); |
||||
|
|
||||
|
export function getDataColumns(): BasicColumn[] { |
||||
|
return [ |
||||
|
{ |
||||
|
title: L('DisplayName:Name'), |
||||
|
dataIndex: 'name', |
||||
|
align: 'left', |
||||
|
width: 350, |
||||
|
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: 180, |
||||
|
resizable: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'defaultValue'), |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:IsVisibleToClients'), |
||||
|
dataIndex: 'isVisibleToClients', |
||||
|
align: 'center', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isVisibleToClients'), |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:IsInherited'), |
||||
|
dataIndex: 'isInherited', |
||||
|
align: 'center', |
||||
|
width: 160, |
||||
|
resizable: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isInherited'), |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:IsEncrypted'), |
||||
|
dataIndex: 'isEncrypted', |
||||
|
align: 'center', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isEncrypted'), |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:IsStatic'), |
||||
|
dataIndex: 'isStatic', |
||||
|
align: 'center', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
defaultHidden: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isStatic'), |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:Providers'), |
||||
|
dataIndex: 'providers', |
||||
|
align: 'left', |
||||
|
width: 160, |
||||
|
resizable: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'providers'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
<template> |
||||
|
<SettingDefinitionTable /> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { defineComponent } from 'vue'; |
||||
|
|
||||
|
import SettingDefinitionTable from './components/SettingDefinitionTable.vue'; |
||||
|
export default defineComponent({ |
||||
|
name: 'SettingDefinitions', |
||||
|
components: { |
||||
|
SettingDefinitionTable, |
||||
|
}, |
||||
|
setup() {}, |
||||
|
}); |
||||
|
</script> |
||||
@ -0,0 +1,10 @@ |
|||||
|
import { Rule } from 'ant-design-vue/lib/form'; |
||||
|
|
||||
|
export interface ModalState { |
||||
|
activeTab: string, |
||||
|
allowedChange: boolean, |
||||
|
entity: Recordable, |
||||
|
entityRules?: Dictionary<string, Rule>, |
||||
|
entityChanged: boolean, |
||||
|
entityEditFlag: boolean, |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
<template> |
||||
|
<SettingForm :save-api="settingFormRef.saveApi" :setting-groups="group"> |
||||
|
<template #send-test-email="{ detail }"> |
||||
|
<FormItem name="testEmail" :label="detail.displayName" :extra="detail.description"> |
||||
|
<SearchInput |
||||
|
:placeholder="L('TargetEmailAddress')" |
||||
|
v-model:value="detail.value" |
||||
|
@search="handleSendTestEmail" |
||||
|
:loading="sendingEmail" |
||||
|
> |
||||
|
<template #enterButton> |
||||
|
<Button type="primary">{{ L('Send') }}</Button> |
||||
|
</template> |
||||
|
</SearchInput> |
||||
|
</FormItem> |
||||
|
</template> |
||||
|
</SettingForm> |
||||
|
</template> |
||||
|
<script lang="ts"> |
||||
|
export default { |
||||
|
name: 'SystemSettings', |
||||
|
} |
||||
|
</script> |
||||
|
<script lang="ts" setup> |
||||
|
import { ref, onMounted } from 'vue'; |
||||
|
import { Button, Form, Input } from 'ant-design-vue'; |
||||
|
import { useAbpStoreWithOut } from '/@/store/modules/abp'; |
||||
|
import { useMessage } from '/@/hooks/web/useMessage'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { SettingForm } from '/@/components/SettingManagement'; |
||||
|
import { SettingGroup } from '/@/api/settings-management/settings/model'; |
||||
|
import { |
||||
|
getCurrentTenantSettings, |
||||
|
getGlobalSettings, |
||||
|
setGlobalSettings, |
||||
|
setCurrentTenantSettings, |
||||
|
sendTestEmail, |
||||
|
} from '/@/api/settings-management/settings'; |
||||
|
import { isEmail } from '/@/utils/is'; |
||||
|
|
||||
|
interface ISettingForm { |
||||
|
providerName: string; |
||||
|
providerKey?: string; |
||||
|
saveApi: (...args: any) => Promise<any>; |
||||
|
} |
||||
|
|
||||
|
const FormItem = Form.Item; |
||||
|
const SearchInput = Input.Search; |
||||
|
|
||||
|
const sendingEmail = ref(false); |
||||
|
const group = ref<SettingGroup[]>([]); |
||||
|
const settingFormRef = ref<ISettingForm>({ |
||||
|
providerName: 'G', |
||||
|
providerKey: '', |
||||
|
saveApi: setGlobalSettings, |
||||
|
}); |
||||
|
const abpStore = useAbpStoreWithOut(); |
||||
|
const { createWarningModal, createMessage } = useMessage(); |
||||
|
const { L } = useLocalization(['AbpSettingManagement']); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
if (abpStore.getApplication.currentTenant.id) { |
||||
|
settingFormRef.value = { |
||||
|
providerName: 'T', |
||||
|
providerKey: abpStore.getApplication.currentTenant.id, |
||||
|
saveApi: setCurrentTenantSettings, |
||||
|
}; |
||||
|
getCurrentTenantSettings().then((res) => { |
||||
|
group.value = res.items; |
||||
|
}); |
||||
|
} else { |
||||
|
getGlobalSettings().then((res) => { |
||||
|
group.value = res.items; |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function handleSendTestEmail(emailAddress: string) { |
||||
|
if (!isEmail(emailAddress)) { |
||||
|
createWarningModal({ |
||||
|
title: L('ValidationErrorMessage'), |
||||
|
content: L('ThisFieldIsNotAValidEmailAddress.') |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
sendingEmail.value = true; |
||||
|
sendTestEmail(emailAddress).then(() => { |
||||
|
createMessage.success(L('SuccessfullySent')); |
||||
|
}).finally(() => { |
||||
|
sendingEmail.value = false; |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
@ -1,173 +0,0 @@ |
|||||
import { useAbpStoreWithOut } from '/@/store/modules/abp'; |
|
||||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|
||||
import { FormProps, FormSchema } from '/@/components/Form'; |
|
||||
import { getList as getResources } from '/@/api/localization/resources'; |
|
||||
import { getList as getLanguages } from '/@/api/localization/languages'; |
|
||||
import { getList as getTexts } from '/@/api/localization/texts'; |
|
||||
import { GetListAsyncByInput as getTemplateDefintions } from '/@/api/text-templating/definitions'; |
|
||||
|
|
||||
const abpStore = useAbpStoreWithOut(); |
|
||||
const { L } = useLocalization(['AbpTextTemplating', 'AbpLocalization', 'AbpUi']); |
|
||||
|
|
||||
export function getSearchFormSchemas(): Partial<FormProps> { |
|
||||
return { |
|
||||
labelWidth: 100, |
|
||||
schemas: [ |
|
||||
{ |
|
||||
field: 'filter', |
|
||||
component: 'Input', |
|
||||
label: L('Search'), |
|
||||
colProps: { span: 24 }, |
|
||||
}, |
|
||||
], |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
export function getModalFormSchemas(): FormSchema[] { |
|
||||
return [ |
|
||||
{ |
|
||||
field: 'concurrencyStamp', |
|
||||
component: 'Input', |
|
||||
label: 'concurrencyStamp', |
|
||||
colProps: { span: 24 }, |
|
||||
show: false, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'isInlineLocalized', |
|
||||
component: 'Checkbox', |
|
||||
label: L('DisplayName:IsInlineLocalized'), |
|
||||
colProps: { span: 24 }, |
|
||||
defaultValue: false, |
|
||||
renderComponentContent: L('DisplayName:IsInlineLocalized'), |
|
||||
}, |
|
||||
{ |
|
||||
field: 'defaultCultureName', |
|
||||
component: 'ApiSelect', |
|
||||
label: L('DisplayName:DefaultCultureName'), |
|
||||
colProps: { span: 24 }, |
|
||||
componentProps: { |
|
||||
api: getLanguages, |
|
||||
params: { |
|
||||
input: { |
|
||||
skipCount: 0, |
|
||||
maxResultCount: 100, |
|
||||
}, |
|
||||
}, |
|
||||
resultField: 'items', |
|
||||
labelField: 'displayName', |
|
||||
valueField: 'cultureName', |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'isLayout', |
|
||||
component: 'Checkbox', |
|
||||
label: L('DisplayName:IsLayout'), |
|
||||
colProps: { span: 24 }, |
|
||||
defaultValue: false, |
|
||||
renderComponentContent: L('DisplayName:IsLayout'), |
|
||||
componentProps: ({ formActionType }) => { |
|
||||
return { |
|
||||
onChange: (e) => { |
|
||||
if (e.target.checked) { |
|
||||
formActionType.setFieldsValue({ |
|
||||
layout: '', |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'layout', |
|
||||
component: 'ApiSelect', |
|
||||
label: L('DisplayName:Layout'), |
|
||||
colProps: { span: 24 }, |
|
||||
ifShow: ({ values }) => { |
|
||||
return values.isLayout === false; |
|
||||
}, |
|
||||
componentProps: { |
|
||||
api: getTemplateDefintions, |
|
||||
params: { |
|
||||
input: { |
|
||||
skipCount: 0, |
|
||||
maxResultCount: 100, |
|
||||
isLayout: true, |
|
||||
}, |
|
||||
}, |
|
||||
resultField: 'items', |
|
||||
labelField: 'displayName', |
|
||||
valueField: 'name', |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'name', |
|
||||
component: 'Input', |
|
||||
label: L('DisplayName:Name'), |
|
||||
colProps: { span: 24 }, |
|
||||
required: true, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'resource', |
|
||||
component: 'ApiSelect', |
|
||||
label: L('DisplayName:ResourceName'), |
|
||||
colProps: { span: 12 }, |
|
||||
required: true, |
|
||||
componentProps: ({ formActionType }) => { |
|
||||
return { |
|
||||
api: getResources, |
|
||||
resultField: 'items', |
|
||||
labelField: 'displayName', |
|
||||
valueField: 'name', |
|
||||
showSearch: true, |
|
||||
filterOption: (input: string, option: any) => { |
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
|
||||
}, |
|
||||
onChange: (val) => { |
|
||||
formActionType.setFieldsValue({ |
|
||||
text: '', |
|
||||
}); |
|
||||
getTexts({ |
|
||||
cultureName: 'en', |
|
||||
targetCultureName: abpStore.getApplication.localization.currentCulture.cultureName, |
|
||||
resourceName: val, |
|
||||
filter: '', |
|
||||
onlyNull: false, |
|
||||
}).then((res) => { |
|
||||
formActionType.updateSchema({ |
|
||||
field: 'text', |
|
||||
componentProps: { |
|
||||
options: res.items.map((item) => { |
|
||||
return { |
|
||||
label: item.targetValue, |
|
||||
value: item.key, |
|
||||
} |
|
||||
}), |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
}, |
|
||||
getPopupContainer: (triggerNode) => triggerNode.parentNode, |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
field: 'text', |
|
||||
component: 'Select', |
|
||||
label: L('DisplayName:DisplayName'), |
|
||||
colProps: { span: 12 }, |
|
||||
required: true, |
|
||||
componentProps: { |
|
||||
showSearch: true, |
|
||||
filterOption: (input: string, option: any) => { |
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0; |
|
||||
}, |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
]; |
|
||||
} |
|
||||
@ -0,0 +1,201 @@ |
|||||
|
<template> |
||||
|
<BasicModal |
||||
|
@register="registerModal" |
||||
|
:title="L('GroupDefinitions')" |
||||
|
:can-fullscreen="false" |
||||
|
:width="600" |
||||
|
: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/webhooks/definitions/groups'; |
||||
|
import { |
||||
|
WebhookGroupDefinitionUpdateDto, |
||||
|
WebhookGroupDefinitionCreateDto |
||||
|
} from '/@/api/webhooks/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(['WebhooksManagement', '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: '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(); |
||||
|
}, |
||||
|
}), |
||||
|
}, |
||||
|
}); |
||||
|
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 = { |
||||
|
isInherited: true, |
||||
|
}; |
||||
|
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 WebhookGroupDefinitionUpdateDto) |
||||
|
: CreateAsyncByInput(cloneDeep(state.entity) as WebhookGroupDefinitionCreateDto); |
||||
|
api.then((res) => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
emits('change', res); |
||||
|
form.resetFields(); |
||||
|
closeModal(); |
||||
|
}).finally(() => { |
||||
|
changeOkLoading(false); |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,147 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #toolbar> |
||||
|
<Button |
||||
|
v-auth="['AbpWebhooks.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: 'AbpWebhooks.GroupDefinitions.Update', |
||||
|
label: L('Edit'), |
||||
|
icon: 'ant-design:edit-outlined', |
||||
|
onClick: handleEdit.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'AbpWebhooks.GroupDefinitions.Delete', |
||||
|
label: L('Delete'), |
||||
|
color: 'error', |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
ifShow: !record.isStatic, |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
:dropDownActions="[ |
||||
|
{ |
||||
|
auth: 'AbpWebhooks.Definitions.Create', |
||||
|
label: L('Webhooks:AddNew'), |
||||
|
icon: 'ant-design:edit-outlined', |
||||
|
onClick: handleAddWebhook.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<GroupDefinitionModal @register="registerModal" @change="fetch" /> |
||||
|
<WebhookDefinitionModal @register="registerWebhookModal" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, 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 { GetListAsyncByInput, DeleteAsyncByName } from '/@/api/webhooks/definitions/groups'; |
||||
|
import { getSearchFormSchemas } from '../datas/ModalData'; |
||||
|
import GroupDefinitionModal from './GroupDefinitionModal.vue'; |
||||
|
import WebhookDefinitionModal from '../../webhooks/components/WebhookDefinitionModal.vue'; |
||||
|
|
||||
|
const { deserialize } = useLocalizationSerializer(); |
||||
|
const { L, Lr } = useLocalization(['WebhooksManagement', 'AbpUi']); |
||||
|
const { createConfirm, createMessage } = useMessage(); |
||||
|
const [registerModal, { openModal }] = useModal(); |
||||
|
const [registerWebhookModal, { openModal: openWebhookModal }] = 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); |
||||
|
setTableData([]); |
||||
|
var input = form.getFieldsValue(); |
||||
|
GetListAsyncByInput(input).then((res) => { |
||||
|
setTableData(res.items); |
||||
|
}).finally(() => { |
||||
|
setLoading(false); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleAddNew() { |
||||
|
openModal(true, {}); |
||||
|
} |
||||
|
|
||||
|
function handleEdit(record) { |
||||
|
openModal(true, record); |
||||
|
} |
||||
|
|
||||
|
function handleAddWebhook(record) { |
||||
|
openWebhookModal(true, { groupName: record.name }); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
createConfirm({ |
||||
|
iconType: 'warning', |
||||
|
title: L('AreYouSure'), |
||||
|
content: L('ItemWillBeDeleteOrRestoreMessage'), |
||||
|
onOk: () => { |
||||
|
return DeleteAsyncByName(record.name).then(() => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
fetch(); |
||||
|
}); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
@ -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 }, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { BasicColumn } from '/@/components/Table'; |
||||
|
import { sorter } from '/@/utils/table'; |
||||
|
|
||||
|
const { L } = useLocalization(['WebhooksManagement']); |
||||
|
|
||||
|
export function getDataColumns(): BasicColumn[] { |
||||
|
return [ |
||||
|
{ |
||||
|
title: L('DisplayName:Name'), |
||||
|
dataIndex: 'name', |
||||
|
align: 'left', |
||||
|
width: 350, |
||||
|
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:IsStatic'), |
||||
|
dataIndex: 'isStatic', |
||||
|
align: 'center', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
defaultHidden: true, |
||||
|
sorter: (a, b) => sorter(a, b, 'isStatic'), |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue