28 changed files with 2155 additions and 16 deletions
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { FeatureDefinitionTable } from '@abp/features'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FeatureDefinitions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<FeatureDefinitionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { Page } from '@vben/common-ui'; |
||||
|
|
||||
|
import { FeatureGroupDefinitionTable } from '@abp/features'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FeatureGroupDefinitions', |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Page> |
||||
|
<FeatureGroupDefinitionTable /> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -1 +1,3 @@ |
|||||
|
export { useFeatureDefinitionsApi } from './useFeatureDefinitionsApi'; |
||||
|
export { useFeatureGroupDefinitionsApi } from './useFeatureGroupDefinitionsApi'; |
||||
export { useFeaturesApi } from './useFeaturesApi'; |
export { useFeaturesApi } from './useFeaturesApi'; |
||||
|
|||||
@ -0,0 +1,100 @@ |
|||||
|
import type { ListResultDto } from '@abp/core'; |
||||
|
|
||||
|
import type { |
||||
|
FeatureDefinitionCreateDto, |
||||
|
FeatureDefinitionDto, |
||||
|
FeatureDefinitionGetListInput, |
||||
|
FeatureDefinitionUpdateDto, |
||||
|
} from '../types/definitions'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useFeatureDefinitionsApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
/** |
||||
|
* 删除功能定义 |
||||
|
* @param name 功能名称 |
||||
|
*/ |
||||
|
function deleteApi(name: string): Promise<void> { |
||||
|
return request(`/api/feature-management/definitions/${name}`, { |
||||
|
method: 'DELETE', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
function getApi(name: string): Promise<FeatureDefinitionDto> { |
||||
|
return request<FeatureDefinitionDto>( |
||||
|
`/api/feature-management/definitions/${name}`, |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询功能定义列表 |
||||
|
* @param input 功能过滤条件 |
||||
|
* @returns 功能定义数据传输对象列表 |
||||
|
*/ |
||||
|
function getListApi( |
||||
|
input?: FeatureDefinitionGetListInput, |
||||
|
): Promise<ListResultDto<FeatureDefinitionDto>> { |
||||
|
return request<ListResultDto<FeatureDefinitionDto>>( |
||||
|
`/api/feature-management/definitions`, |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建功能定义 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
function createApi( |
||||
|
input: FeatureDefinitionCreateDto, |
||||
|
): Promise<FeatureDefinitionDto> { |
||||
|
return request<FeatureDefinitionDto>( |
||||
|
'/api/feature-management/definitions', |
||||
|
{ |
||||
|
data: input, |
||||
|
method: 'POST', |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新功能定义 |
||||
|
* @param name 功能名称 |
||||
|
* @param input 功能定义参数 |
||||
|
* @returns 功能定义数据传输对象 |
||||
|
*/ |
||||
|
function updateApi( |
||||
|
name: string, |
||||
|
input: FeatureDefinitionUpdateDto, |
||||
|
): Promise<FeatureDefinitionDto> { |
||||
|
return request<FeatureDefinitionDto>( |
||||
|
`/api/feature-management/definitions/${name}`, |
||||
|
{ |
||||
|
data: input, |
||||
|
method: 'PUT', |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
createApi, |
||||
|
deleteApi, |
||||
|
getApi, |
||||
|
getListApi, |
||||
|
updateApi, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,467 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { |
||||
|
PropertyInfo, |
||||
|
SelectionStringValueItem, |
||||
|
StringValueTypeInstance, |
||||
|
} from '@abp/ui'; |
||||
|
import type { FormInstance } from 'ant-design-vue'; |
||||
|
|
||||
|
import type { FeatureDefinitionDto } from '../../../types/definitions'; |
||||
|
import type { FeatureGroupDefinitionDto } from '../../../types/groups'; |
||||
|
|
||||
|
import { |
||||
|
defineEmits, |
||||
|
defineOptions, |
||||
|
reactive, |
||||
|
ref, |
||||
|
toValue, |
||||
|
unref, |
||||
|
useTemplateRef, |
||||
|
} from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
import { isBoolean, isString } from '@vben/utils'; |
||||
|
|
||||
|
import { |
||||
|
listToTree, |
||||
|
useLocalization, |
||||
|
useLocalizationSerializer, |
||||
|
useValidation, |
||||
|
ValidationEnum, |
||||
|
} from '@abp/core'; |
||||
|
import { LocalizableInput, PropertyTable, StringValueTypeInput } from '@abp/ui'; |
||||
|
import { |
||||
|
Checkbox, |
||||
|
Form, |
||||
|
Input, |
||||
|
InputNumber, |
||||
|
message, |
||||
|
Select, |
||||
|
Tabs, |
||||
|
Textarea, |
||||
|
TreeSelect, |
||||
|
} from 'ant-design-vue'; |
||||
|
|
||||
|
import { useFeatureDefinitionsApi } from '../../../api/useFeatureDefinitionsApi'; |
||||
|
import { useFeatureGroupDefinitionsApi } from '../../../api/useFeatureGroupDefinitionsApi'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FeatureDefinitionModal', |
||||
|
}); |
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'change', data: FeatureDefinitionDto): void; |
||||
|
}>(); |
||||
|
|
||||
|
const FormItem = Form.Item; |
||||
|
const TabPane = Tabs.TabPane; |
||||
|
|
||||
|
interface DefinitionTreeVo { |
||||
|
children: DefinitionTreeVo[]; |
||||
|
displayName: string; |
||||
|
groupName: string; |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
type TabKeys = 'basic' | 'props'; |
||||
|
|
||||
|
const defaultModel = { |
||||
|
allowedProviders: [], |
||||
|
displayName: '', |
||||
|
extraProperties: {}, |
||||
|
groupName: '', |
||||
|
isAvailableToHost: true, |
||||
|
isEnabled: true, |
||||
|
isStatic: false, |
||||
|
isVisibleToClients: false, |
||||
|
name: '', |
||||
|
valueType: '', |
||||
|
} as FeatureDefinitionDto; |
||||
|
|
||||
|
const isEditModel = ref(false); |
||||
|
const validatorNameRef = ref<string>('NULL'); |
||||
|
const valueTypeNameRef = ref<string>('FreeTextStringValueType'); |
||||
|
const activeTab = ref<TabKeys>('basic'); |
||||
|
const form = useTemplateRef<FormInstance>('form'); |
||||
|
const valueTypeInput = useTemplateRef<StringValueTypeInstance>('valueType'); |
||||
|
const formModel = ref<FeatureDefinitionDto>({ ...defaultModel }); |
||||
|
const availableGroups = ref<FeatureGroupDefinitionDto[]>([]); |
||||
|
const availableDefinitions = ref<DefinitionTreeVo[]>([]); |
||||
|
const selectionDataSource = ref<{ label: string; value: string }[]>([]); |
||||
|
|
||||
|
const { Lr } = useLocalization(); |
||||
|
const { deserialize, validate } = useLocalizationSerializer(); |
||||
|
const { getListApi: getGroupsApi } = useFeatureGroupDefinitionsApi(); |
||||
|
const { defineValidator, fieldRequired, mapEnumValidMessage } = useValidation(); |
||||
|
const { |
||||
|
createApi, |
||||
|
getApi, |
||||
|
getListApi: getDefinitionsApi, |
||||
|
updateApi, |
||||
|
} = useFeatureDefinitionsApi(); |
||||
|
const formRules = reactive({ |
||||
|
defaultValue: defineValidator({ |
||||
|
trigger: 'change', |
||||
|
validator(_rule, value) { |
||||
|
const valueType = unref(valueTypeInput); |
||||
|
if (valueType) { |
||||
|
return valueType.validate(value); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}), |
||||
|
// description: defineValidator({ |
||||
|
// trigger: 'blur', |
||||
|
// validator(_rule, value) { |
||||
|
// if (!validate(value, { required: false })) { |
||||
|
// return Promise.reject( |
||||
|
// $t(ValidationEnum.FieldRequired, [$t('DisplayName:Description')]), |
||||
|
// ); |
||||
|
// } |
||||
|
// return Promise.resolve(); |
||||
|
// }, |
||||
|
// }), |
||||
|
displayName: defineValidator({ |
||||
|
required: true, |
||||
|
trigger: 'blur', |
||||
|
validator(_rule, value) { |
||||
|
if (!validate(value)) { |
||||
|
return Promise.reject( |
||||
|
mapEnumValidMessage(ValidationEnum.FieldRequired, [ |
||||
|
$t('AbpFeatureManagement.DisplayName:DisplayName'), |
||||
|
]), |
||||
|
); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}), |
||||
|
groupName: fieldRequired({ |
||||
|
name: 'GroupName', |
||||
|
prefix: 'DisplayName', |
||||
|
resourceName: 'AbpFeatureManagement', |
||||
|
trigger: 'blur', |
||||
|
}), |
||||
|
name: fieldRequired({ |
||||
|
name: 'Name', |
||||
|
prefix: 'DisplayName', |
||||
|
resourceName: 'AbpFeatureManagement', |
||||
|
trigger: 'blur', |
||||
|
}), |
||||
|
}); |
||||
|
|
||||
|
const [Modal, modalApi] = useVbenModal({ |
||||
|
class: 'w-1/2', |
||||
|
draggable: true, |
||||
|
fullscreenButton: false, |
||||
|
onCancel() { |
||||
|
modalApi.close(); |
||||
|
}, |
||||
|
onConfirm: async () => { |
||||
|
await form.value?.validate(); |
||||
|
const input = toValue(formModel); |
||||
|
if (input.defaultValue && !isString(input.defaultValue)) { |
||||
|
input.defaultValue = String(input.defaultValue); |
||||
|
} |
||||
|
const api = isEditModel.value |
||||
|
? updateApi(formModel.value.name, input) |
||||
|
: createApi(input); |
||||
|
modalApi.setState({ confirmLoading: true, loading: true }); |
||||
|
api |
||||
|
.then((res) => { |
||||
|
message.success($t('AbpUi.SavedSuccessfully')); |
||||
|
emits('change', res); |
||||
|
modalApi.close(); |
||||
|
}) |
||||
|
.finally(() => { |
||||
|
modalApi.setState({ confirmLoading: false, loading: false }); |
||||
|
}); |
||||
|
}, |
||||
|
onOpenChange: async (isOpen: boolean) => { |
||||
|
if (isOpen) { |
||||
|
isEditModel.value = false; |
||||
|
activeTab.value = 'basic'; |
||||
|
formModel.value = { ...defaultModel }; |
||||
|
availableDefinitions.value = []; |
||||
|
availableGroups.value = []; |
||||
|
modalApi.setState({ |
||||
|
showConfirmButton: true, |
||||
|
title: $t('AbpFeatureManagement.FeatureDefinitions:AddNew'), |
||||
|
}); |
||||
|
try { |
||||
|
modalApi.setState({ loading: true }); |
||||
|
const { groupName, name } = modalApi.getData<FeatureDefinitionDto>(); |
||||
|
name && (await onGet(name)); |
||||
|
await onInitGroups(groupName); |
||||
|
} finally { |
||||
|
modalApi.setState({ loading: false }); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
title: $t('AbpFeatureManagement.FeatureDefinitions:AddNew'), |
||||
|
}); |
||||
|
async function onInitGroups(name?: string) { |
||||
|
const { items } = await getGroupsApi({ filter: name }); |
||||
|
availableGroups.value = items.map((group) => { |
||||
|
const localizableGroup = deserialize(group.displayName); |
||||
|
return { |
||||
|
...group, |
||||
|
displayName: Lr(localizableGroup.resourceName, localizableGroup.name), |
||||
|
}; |
||||
|
}); |
||||
|
if (name) { |
||||
|
formModel.value.groupName = name; |
||||
|
await onGroupChange(name); |
||||
|
} |
||||
|
} |
||||
|
async function onGet(name: string) { |
||||
|
isEditModel.value = true; |
||||
|
const dto = await getApi(name); |
||||
|
formModel.value = dto; |
||||
|
modalApi.setState({ |
||||
|
showConfirmButton: !dto.isStatic, |
||||
|
title: `${$t('AbpFeatureManagement.FeatureDefinitions')} - ${dto.name}`, |
||||
|
}); |
||||
|
} |
||||
|
async function onGroupChange(name?: string) { |
||||
|
const { items } = await getDefinitionsApi({ |
||||
|
groupName: name, |
||||
|
}); |
||||
|
const features = items.map((item) => { |
||||
|
const displayName = deserialize(item.displayName); |
||||
|
const description = deserialize(item.description); |
||||
|
return { |
||||
|
...item, |
||||
|
description: Lr(description.resourceName, description.name), |
||||
|
disabled: item.name === formModel.value.name, |
||||
|
displayName: Lr(displayName.resourceName, displayName.name), |
||||
|
}; |
||||
|
}); |
||||
|
availableDefinitions.value = listToTree(features, { |
||||
|
id: 'name', |
||||
|
pid: 'parentName', |
||||
|
}); |
||||
|
} |
||||
|
function onPropChange(prop: PropertyInfo) { |
||||
|
formModel.value.extraProperties ??= {}; |
||||
|
formModel.value.extraProperties[prop.key] = prop.value; |
||||
|
} |
||||
|
function onPropDelete(prop: PropertyInfo) { |
||||
|
formModel.value.extraProperties ??= {}; |
||||
|
delete formModel.value.extraProperties[prop.key]; |
||||
|
} |
||||
|
function onValueTypeNameChange(valueTypeName: string) { |
||||
|
valueTypeNameRef.value = valueTypeName; |
||||
|
switch (valueTypeName) { |
||||
|
case 'ToggleStringValueType': { |
||||
|
if (!isBoolean(formModel.value.defaultValue)) { |
||||
|
formModel.value.defaultValue ??= 'false'; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
formModel.value.defaultValue ??= undefined; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
form.value?.clearValidate(); |
||||
|
} |
||||
|
|
||||
|
function onValidatorNameChange(validatorName: string) { |
||||
|
validatorNameRef.value = validatorName; |
||||
|
} |
||||
|
|
||||
|
function onSelectionChange(items: SelectionStringValueItem[]) { |
||||
|
if (items.length === 0) { |
||||
|
formModel.value.defaultValue = undefined; |
||||
|
selectionDataSource.value = []; |
||||
|
return; |
||||
|
} |
||||
|
selectionDataSource.value = items.map((item) => { |
||||
|
return { |
||||
|
label: Lr(item.displayText.resourceName, item.displayText.name), |
||||
|
value: item.value, |
||||
|
}; |
||||
|
}); |
||||
|
if (!items.some((item) => item.value === formModel.value.defaultValue)) { |
||||
|
formModel.value.defaultValue = undefined; |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Modal> |
||||
|
<Form |
||||
|
ref="form" |
||||
|
:model="formModel" |
||||
|
:rules="formRules" |
||||
|
:label-col="{ span: 6 }" |
||||
|
:wrapper-col="{ span: 18 }" |
||||
|
> |
||||
|
<Tabs v-model:active-key="activeTab"> |
||||
|
<!-- 基本信息 --> |
||||
|
<TabPane key="basic" :tab="$t('AbpFeatureManagement.BasicInfo')"> |
||||
|
<FormItem |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:GroupName')" |
||||
|
name="groupName" |
||||
|
> |
||||
|
<Select |
||||
|
v-model:value="formModel.groupName" |
||||
|
:allow-clear="true" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:field-names="{ |
||||
|
label: 'displayName', |
||||
|
value: 'name', |
||||
|
}" |
||||
|
:options="availableGroups" |
||||
|
@change="(e) => onGroupChange(e?.toString())" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
v-if="availableDefinitions.length > 0" |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:ParentName')" |
||||
|
name="parentName" |
||||
|
> |
||||
|
<TreeSelect |
||||
|
v-model:value="formModel.parentName" |
||||
|
:allow-clear="true" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:field-names="{ |
||||
|
label: 'displayName', |
||||
|
value: 'name', |
||||
|
children: 'children', |
||||
|
}" |
||||
|
:tree-data="availableDefinitions" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:Name')" |
||||
|
name="name" |
||||
|
> |
||||
|
<Input |
||||
|
v-model:value="formModel.name" |
||||
|
:disabled="formModel.isStatic" |
||||
|
autocomplete="off" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:DisplayName')" |
||||
|
name="displayName" |
||||
|
> |
||||
|
<LocalizableInput |
||||
|
v-model:value="formModel.displayName" |
||||
|
:disabled="formModel.isStatic" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:Description')" |
||||
|
name="description" |
||||
|
> |
||||
|
<LocalizableInput |
||||
|
v-model:value="formModel.description" |
||||
|
:disabled="formModel.isStatic" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="defaultValue" |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:DefaultValue')" |
||||
|
> |
||||
|
<Select |
||||
|
v-if="valueTypeNameRef === 'SelectionStringValueType'" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:allow-clear="true" |
||||
|
v-model:value="formModel.defaultValue" |
||||
|
:options="selectionDataSource" |
||||
|
/> |
||||
|
<Textarea |
||||
|
v-else-if=" |
||||
|
valueTypeNameRef === 'FreeTextStringValueType' && |
||||
|
(validatorNameRef === 'NULL' || validatorNameRef === 'STRING') |
||||
|
" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:allow-clear="true" |
||||
|
:auto-size="{ minRows: 3 }" |
||||
|
v-model:value="formModel.defaultValue" |
||||
|
/> |
||||
|
<InputNumber |
||||
|
v-else-if=" |
||||
|
valueTypeNameRef === 'FreeTextStringValueType' && |
||||
|
validatorNameRef === 'NUMERIC' |
||||
|
" |
||||
|
style="width: 100%" |
||||
|
:disabled="formModel.isStatic" |
||||
|
v-model:value="formModel.defaultValue" |
||||
|
/> |
||||
|
<Checkbox |
||||
|
v-else-if=" |
||||
|
valueTypeNameRef === 'ToggleStringValueType' && |
||||
|
validatorNameRef === 'BOOLEAN' |
||||
|
" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:checked="formModel.defaultValue === 'true'" |
||||
|
@change=" |
||||
|
(e) => |
||||
|
(formModel.defaultValue = String( |
||||
|
e.target.checked, |
||||
|
).toLowerCase()) |
||||
|
" |
||||
|
> |
||||
|
{{ $t('AbpFeatureManagement.DisplayName:DefaultValue') }} |
||||
|
</Checkbox> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="isVisibleToClients" |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:IsVisibleToClients')" |
||||
|
:extra="$t('AbpFeatureManagement.Description:IsVisibleToClients')" |
||||
|
> |
||||
|
<Checkbox v-model:checked="formModel.isVisibleToClients"> |
||||
|
{{ $t('AbpFeatureManagement.DisplayName:IsVisibleToClients') }} |
||||
|
</Checkbox> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="isAvailableToHost" |
||||
|
:label="$t('AbpFeatureManagement.DisplayName:IsAvailableToHost')" |
||||
|
:extra="$t('AbpFeatureManagement.Description:IsAvailableToHost')" |
||||
|
> |
||||
|
<Checkbox v-model:checked="formModel.isAvailableToHost"> |
||||
|
{{ $t('AbpFeatureManagement.DisplayName:IsAvailableToHost') }} |
||||
|
</Checkbox> |
||||
|
</FormItem> |
||||
|
</TabPane> |
||||
|
<TabPane |
||||
|
key="valueType" |
||||
|
:tab="$t('AbpFeatureManagement.ValueValidator')" |
||||
|
force-render |
||||
|
> |
||||
|
<FormItem |
||||
|
name="valueType" |
||||
|
label="" |
||||
|
:label-col="{ span: 0 }" |
||||
|
:wrapper-col="{ span: 24 }" |
||||
|
> |
||||
|
<StringValueTypeInput |
||||
|
ref="valueType" |
||||
|
:disabled="formModel.isStatic" |
||||
|
:allow-delete="true" |
||||
|
:allow-edit="true" |
||||
|
v-model:value="formModel.valueType" |
||||
|
@change:value-type="onValueTypeNameChange" |
||||
|
@change:validator="onValidatorNameChange" |
||||
|
@change:selection="onSelectionChange" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</TabPane> |
||||
|
<!-- 属性 --> |
||||
|
<TabPane key="props" :tab="$t('AbpFeatureManagement.Properties')"> |
||||
|
<PropertyTable |
||||
|
:data="formModel.extraProperties" |
||||
|
:disabled="formModel.isStatic" |
||||
|
@change="onPropChange" |
||||
|
@delete="onPropDelete" |
||||
|
/> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,346 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { FeatureDefinitionDto } from '../../../types/definitions'; |
||||
|
import type { FeatureGroupDefinitionDto } from '../../../types/groups'; |
||||
|
|
||||
|
import { defineAsyncComponent, h, onMounted, reactive, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenModal } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { |
||||
|
listToTree, |
||||
|
useLocalization, |
||||
|
useLocalizationSerializer, |
||||
|
} from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { |
||||
|
CheckOutlined, |
||||
|
CloseOutlined, |
||||
|
DeleteOutlined, |
||||
|
EditOutlined, |
||||
|
PlusOutlined, |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { Button, message, Modal } from 'ant-design-vue'; |
||||
|
import { VxeGrid } from 'vxe-table'; |
||||
|
|
||||
|
import { useFeatureDefinitionsApi } from '../../../api/useFeatureDefinitionsApi'; |
||||
|
import { useFeatureGroupDefinitionsApi } from '../../../api/useFeatureGroupDefinitionsApi'; |
||||
|
import { GroupDefinitionsPermissions } from '../../../constants/permissions'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'FeatureDefinitionTable', |
||||
|
}); |
||||
|
|
||||
|
interface PermissionVo { |
||||
|
children: PermissionVo[]; |
||||
|
displayName: string; |
||||
|
groupName: string; |
||||
|
isEnabled: boolean; |
||||
|
isStatic: boolean; |
||||
|
name: string; |
||||
|
parentName?: string; |
||||
|
providers: string[]; |
||||
|
stateCheckers: string[]; |
||||
|
} |
||||
|
interface PermissionGroupVo { |
||||
|
displayName: string; |
||||
|
name: string; |
||||
|
permissions: PermissionVo[]; |
||||
|
} |
||||
|
|
||||
|
const permissionGroups = ref<PermissionGroupVo[]>([]); |
||||
|
const pageState = reactive({ |
||||
|
current: 1, |
||||
|
size: 10, |
||||
|
total: 0, |
||||
|
}); |
||||
|
|
||||
|
const { Lr } = useLocalization(); |
||||
|
const { deserialize } = useLocalizationSerializer(); |
||||
|
const { getListApi: getGroupsApi } = useFeatureGroupDefinitionsApi(); |
||||
|
const { deleteApi, getListApi: getPermissionsApi } = useFeatureDefinitionsApi(); |
||||
|
|
||||
|
const formOptions: VbenFormProps = { |
||||
|
// 默认展开 |
||||
|
collapsed: false, |
||||
|
handleReset: onReset, |
||||
|
async handleSubmit(params) { |
||||
|
pageState.current = 1; |
||||
|
await onGet(params); |
||||
|
}, |
||||
|
schema: [ |
||||
|
{ |
||||
|
component: 'Input', |
||||
|
fieldName: 'filter', |
||||
|
formItemClass: 'col-span-2 items-baseline', |
||||
|
label: $t('AbpUi.Search'), |
||||
|
}, |
||||
|
], |
||||
|
// 控制表单是否显示折叠按钮 |
||||
|
showCollapseButton: true, |
||||
|
// 按下回车时是否提交表单 |
||||
|
submitOnEnter: true, |
||||
|
}; |
||||
|
|
||||
|
const gridOptions: VxeGridProps<FeatureGroupDefinitionDto> = { |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'center', |
||||
|
type: 'seq', |
||||
|
width: 50, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'group', |
||||
|
slots: { content: 'group' }, |
||||
|
type: 'expand', |
||||
|
width: 50, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'name', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:Name'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'displayName', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:DisplayName'), |
||||
|
}, |
||||
|
], |
||||
|
expandConfig: { |
||||
|
padding: true, |
||||
|
trigger: 'row', |
||||
|
}, |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
toolbarConfig: { |
||||
|
custom: true, |
||||
|
export: true, |
||||
|
refresh: false, |
||||
|
zoom: true, |
||||
|
}, |
||||
|
}; |
||||
|
const subGridColumns: VxeGridProps<FeatureDefinitionDto>['columns'] = [ |
||||
|
{ |
||||
|
align: 'center', |
||||
|
type: 'seq', |
||||
|
width: 50, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'name', |
||||
|
minWidth: 150, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:Name'), |
||||
|
treeNode: true, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'displayName', |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:DisplayName'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'description', |
||||
|
minWidth: 120, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:Description'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'isVisibleToClients', |
||||
|
minWidth: 120, |
||||
|
slots: { default: 'isVisibleToClients' }, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:IsVisibleToClients'), |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'isAvailableToHost', |
||||
|
minWidth: 120, |
||||
|
slots: { default: 'isAvailableToHost' }, |
||||
|
title: $t('AbpFeatureManagement.DisplayName:IsAvailableToHost'), |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 180, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
const gridEvents: VxeGridListeners<FeatureGroupDefinitionDto> = { |
||||
|
pageChange(params) { |
||||
|
pageState.current = params.currentPage; |
||||
|
pageState.size = params.pageSize; |
||||
|
onPageChange(); |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const [GroupGrid, gridApi] = useVbenVxeGrid({ |
||||
|
formOptions, |
||||
|
gridEvents, |
||||
|
gridOptions, |
||||
|
}); |
||||
|
|
||||
|
const [FeatureDefinitionModal, modalApi] = useVbenModal({ |
||||
|
connectedComponent: defineAsyncComponent( |
||||
|
() => import('./FeatureDefinitionModal.vue'), |
||||
|
), |
||||
|
}); |
||||
|
|
||||
|
async function onGet(input?: Record<string, string>) { |
||||
|
try { |
||||
|
gridApi.setLoading(true); |
||||
|
const groupRes = await getGroupsApi(input); |
||||
|
const permissionRes = await getPermissionsApi(input); |
||||
|
pageState.total = groupRes.items.length; |
||||
|
permissionGroups.value = groupRes.items.map((group) => { |
||||
|
const localizableGroup = deserialize(group.displayName); |
||||
|
const permissions = permissionRes.items |
||||
|
.filter((permission) => permission.groupName === group.name) |
||||
|
.map((permission) => { |
||||
|
const displayName = deserialize(permission.displayName); |
||||
|
const description = deserialize(permission.displayName); |
||||
|
return { |
||||
|
...permission, |
||||
|
description: Lr(description.resourceName, description.name), |
||||
|
displayName: Lr(displayName.resourceName, displayName.name), |
||||
|
}; |
||||
|
}); |
||||
|
return { |
||||
|
...group, |
||||
|
displayName: Lr(localizableGroup.resourceName, localizableGroup.name), |
||||
|
permissions: listToTree(permissions, { |
||||
|
id: 'name', |
||||
|
pid: 'parentName', |
||||
|
}), |
||||
|
}; |
||||
|
}); |
||||
|
onPageChange(); |
||||
|
} finally { |
||||
|
gridApi.setLoading(false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onReset() { |
||||
|
await gridApi.formApi.resetForm(); |
||||
|
const input = await gridApi.formApi.getValues(); |
||||
|
await onGet(input); |
||||
|
} |
||||
|
|
||||
|
function onPageChange() { |
||||
|
const items = permissionGroups.value.slice( |
||||
|
(pageState.current - 1) * pageState.size, |
||||
|
pageState.current * pageState.size, |
||||
|
); |
||||
|
gridApi.setGridOptions({ |
||||
|
data: items, |
||||
|
pagerConfig: { |
||||
|
currentPage: pageState.current, |
||||
|
pageSize: pageState.size, |
||||
|
total: pageState.total, |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function onCreate() { |
||||
|
modalApi.setData({}); |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onUpdate(row: FeatureDefinitionDto) { |
||||
|
modalApi.setData(row); |
||||
|
modalApi.open(); |
||||
|
} |
||||
|
|
||||
|
function onDelete(row: FeatureDefinitionDto) { |
||||
|
Modal.confirm({ |
||||
|
centered: true, |
||||
|
content: `${$t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.name])}`, |
||||
|
onOk: async () => { |
||||
|
await deleteApi(row.name); |
||||
|
message.success($t('AbpUi.SuccessfullyDeleted')); |
||||
|
onGet(); |
||||
|
}, |
||||
|
title: $t('AbpUi.AreYouSure'), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
onMounted(onGet); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<GroupGrid :table-title="$t('AbpFeatureManagement.FeatureDefinitions')"> |
||||
|
<template #toolbar-tools> |
||||
|
<Button |
||||
|
:icon="h(PlusOutlined)" |
||||
|
type="primary" |
||||
|
v-access:code="[GroupDefinitionsPermissions.Create]" |
||||
|
@click="onCreate" |
||||
|
> |
||||
|
{{ $t('AbpFeatureManagement.FeatureDefinitions:AddNew') }} |
||||
|
</Button> |
||||
|
</template> |
||||
|
<template #group="{ row: group }"> |
||||
|
<VxeGrid |
||||
|
:columns="subGridColumns" |
||||
|
:data="group.permissions" |
||||
|
:tree-config="{ |
||||
|
trigger: 'row', |
||||
|
rowField: 'name', |
||||
|
childrenField: 'children', |
||||
|
}" |
||||
|
> |
||||
|
<template #isVisibleToClients="{ row }"> |
||||
|
<div class="flex flex-row justify-center"> |
||||
|
<CheckOutlined |
||||
|
v-if="row.isVisibleToClients" |
||||
|
class="text-green-500" |
||||
|
/> |
||||
|
<CloseOutlined v-else class="text-red-500" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #isAvailableToHost="{ row }"> |
||||
|
<div class="flex flex-row justify-center"> |
||||
|
<CheckOutlined |
||||
|
v-if="row.isAvailableToHost" |
||||
|
class="text-green-500" |
||||
|
/> |
||||
|
<CloseOutlined v-else class="text-red-500" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #action="{ row: permission }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
:icon="h(EditOutlined)" |
||||
|
block |
||||
|
type="link" |
||||
|
v-access:code="[GroupDefinitionsPermissions.Update]" |
||||
|
@click="onUpdate(permission)" |
||||
|
> |
||||
|
{{ $t('AbpUi.Edit') }} |
||||
|
</Button> |
||||
|
<Button |
||||
|
v-if="!permission.isStatic" |
||||
|
:icon="h(DeleteOutlined)" |
||||
|
block |
||||
|
danger |
||||
|
type="link" |
||||
|
v-access:code="[GroupDefinitionsPermissions.Delete]" |
||||
|
@click="onDelete(permission)" |
||||
|
> |
||||
|
{{ $t('AbpUi.Delete') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</VxeGrid> |
||||
|
</template> |
||||
|
</GroupGrid> |
||||
|
<FeatureDefinitionModal @change="() => onGet()" /> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -1,2 +1,3 @@ |
|||||
|
export { default as FeatureDefinitionTable } from './definitions/features/FeatureDefinitionTable.vue'; |
||||
export { default as FeatureGroupDefinitionTable } from './definitions/groups/FeatureGroupDefinitionTable.vue'; |
export { default as FeatureGroupDefinitionTable } from './definitions/groups/FeatureGroupDefinitionTable.vue'; |
||||
export { default as FeatureModal } from './features/FeatureModal.vue'; |
export { default as FeatureModal } from './features/FeatureModal.vue'; |
||||
|
|||||
@ -0,0 +1,47 @@ |
|||||
|
import type { IHasConcurrencyStamp, IHasExtraProperties } from '@abp/core'; |
||||
|
|
||||
|
interface FeatureDefinitionDto extends IHasExtraProperties { |
||||
|
allowedProviders: string[]; |
||||
|
defaultValue?: string; |
||||
|
description?: string; |
||||
|
displayName: string; |
||||
|
groupName: string; |
||||
|
isAvailableToHost: boolean; |
||||
|
isStatic: boolean; |
||||
|
isVisibleToClients: boolean; |
||||
|
name: string; |
||||
|
parentName?: string; |
||||
|
valueType: string; |
||||
|
} |
||||
|
|
||||
|
interface FeatureDefinitionGetListInput { |
||||
|
filter?: string; |
||||
|
groupName?: string; |
||||
|
} |
||||
|
|
||||
|
interface FeatureDefinitionCreateOrUpdateDto extends IHasExtraProperties { |
||||
|
allowedProviders: string[]; |
||||
|
defaultValue?: string; |
||||
|
description?: string; |
||||
|
displayName: string; |
||||
|
isAvailableToHost: boolean; |
||||
|
isVisibleToClients: boolean; |
||||
|
parentName?: string; |
||||
|
valueType: string; |
||||
|
} |
||||
|
|
||||
|
interface FeatureDefinitionUpdateDto |
||||
|
extends FeatureDefinitionCreateOrUpdateDto, |
||||
|
IHasConcurrencyStamp {} |
||||
|
interface FeatureDefinitionCreateDto |
||||
|
extends FeatureDefinitionCreateOrUpdateDto { |
||||
|
groupName: string; |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
export type { |
||||
|
FeatureDefinitionCreateDto, |
||||
|
FeatureDefinitionDto, |
||||
|
FeatureDefinitionGetListInput, |
||||
|
FeatureDefinitionUpdateDto, |
||||
|
}; |
||||
@ -1 +1 @@ |
|||||
export * from './useGdprRequestsApi'; |
export { useGdprRequestsApi } from './useGdprRequestsApi'; |
||||
|
|||||
@ -1,2 +1,2 @@ |
|||||
export * from './useEmailMessagesApi'; |
export { useEmailMessagesApi } from './useEmailMessagesApi'; |
||||
export * from './useSmsMessagesApi'; |
export { useSmsMessagesApi } from './useSmsMessagesApi'; |
||||
|
|||||
@ -1,4 +1,5 @@ |
|||||
export { default as LocalizableInput } from './localizable-input/LocalizableInput.vue'; |
export { default as LocalizableInput } from './localizable-input/LocalizableInput.vue'; |
||||
export { default as PropertyTable } from './properties/PropertyTable.vue'; |
export { default as PropertyTable } from './properties/PropertyTable.vue'; |
||||
export * from './properties/types'; |
export * from './properties/types'; |
||||
|
export * from './string-value-type'; |
||||
export type * from './vxe-table'; |
export type * from './vxe-table'; |
||||
|
|||||
@ -0,0 +1,765 @@ |
|||||
|
<!-- eslint-disable vue/custom-event-name-casing --> |
||||
|
<script setup lang="ts"> |
||||
|
import type { LocalizableStringInfo } from '@abp/core'; |
||||
|
import type { RuleObject } from 'ant-design-vue/lib/form'; |
||||
|
import type { ColumnsType } from 'ant-design-vue/lib/table'; |
||||
|
|
||||
|
import type { StringValueTypeInstance } from './interface'; |
||||
|
import type { SelectionStringValueItem, StringValueType } from './valueType'; |
||||
|
|
||||
|
import { computed, reactive, ref, unref, watch } from 'vue'; |
||||
|
|
||||
|
import { createIconifyIcon } from '@vben/icons'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { |
||||
|
isNullOrWhiteSpace, |
||||
|
useLocalization, |
||||
|
useLocalizationSerializer, |
||||
|
} from '@abp/core'; |
||||
|
import { |
||||
|
Button, |
||||
|
Card, |
||||
|
Checkbox, |
||||
|
Col, |
||||
|
Form, |
||||
|
Input, |
||||
|
InputNumber, |
||||
|
Modal, |
||||
|
Row, |
||||
|
Select, |
||||
|
Table, |
||||
|
} from 'ant-design-vue'; |
||||
|
|
||||
|
import LocalizableInput from '../localizable-input/LocalizableInput.vue'; |
||||
|
import { |
||||
|
AlwaysValidValueValidator, |
||||
|
BooleanValueValidator, |
||||
|
NumericValueValidator, |
||||
|
StringValueValidator, |
||||
|
} from './validator'; |
||||
|
import { |
||||
|
FreeTextStringValueType, |
||||
|
SelectionStringValueType, |
||||
|
ToggleStringValueType, |
||||
|
valueTypeSerializer, |
||||
|
} from './valueType'; |
||||
|
|
||||
|
interface Props { |
||||
|
allowDelete: boolean; |
||||
|
allowEdit: boolean; |
||||
|
disabled: boolean; |
||||
|
value: string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
allowDelete: false, |
||||
|
allowEdit: false, |
||||
|
disabled: false, |
||||
|
value: '{}', |
||||
|
}); |
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'update:value', data: string | undefined): void; |
||||
|
(event: 'change', data: string | undefined): void; |
||||
|
(event: 'change:valueType', data: string): void; |
||||
|
(event: 'change:validator', data: string): void; |
||||
|
(event: 'change:selection', data: SelectionStringValueItem[]): void; |
||||
|
}>(); |
||||
|
const FormItemRest = Form.ItemRest; |
||||
|
const FormItem = Form.Item; |
||||
|
const Option = Select.Option; |
||||
|
const DeleteOutlined = createIconifyIcon('ant-design:delete-outlined'); |
||||
|
const EditOutlined = createIconifyIcon('ant-design:edit-outlined'); |
||||
|
|
||||
|
const { Lr } = useLocalization(); |
||||
|
const { |
||||
|
deserialize, |
||||
|
serialize, |
||||
|
validate: validateLocalizer, |
||||
|
} = useLocalizationSerializer(); |
||||
|
interface Selection { |
||||
|
form: { |
||||
|
editFlag: boolean; |
||||
|
model: any; |
||||
|
rules?: Record<string, RuleObject>; |
||||
|
}; |
||||
|
modal: { |
||||
|
maskClosable?: boolean; |
||||
|
minHeight?: number; |
||||
|
onCancel?: (e: MouseEvent) => void; |
||||
|
onOk?: (e: MouseEvent) => void; |
||||
|
title?: string; |
||||
|
visible?: boolean; |
||||
|
width?: number; |
||||
|
}; |
||||
|
} |
||||
|
interface State { |
||||
|
selection: Selection; |
||||
|
value?: string; |
||||
|
valueType: StringValueType; |
||||
|
} |
||||
|
|
||||
|
const formRef = ref<any>(); |
||||
|
const state = reactive<State>({ |
||||
|
selection: { |
||||
|
form: { |
||||
|
editFlag: false, |
||||
|
model: {}, |
||||
|
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.some((x) => serialize(x.displayText) === value)) { |
||||
|
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.some((x) => x.value === value)) { |
||||
|
return Promise.reject( |
||||
|
$t( |
||||
|
'component.value_type_nput.type.SELECTION.duplicateKeyOrValue', |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
return Promise.resolve(); |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
modal: { |
||||
|
maskClosable: false, |
||||
|
minHeight: 400, |
||||
|
onCancel: handleCancel, |
||||
|
onOk: handleSubmit, |
||||
|
title: $t('component.value_type_nput.type.SELECTION.modal.title'), |
||||
|
visible: false, |
||||
|
width: 600, |
||||
|
}, |
||||
|
}, |
||||
|
valueType: new FreeTextStringValueType(), |
||||
|
}); |
||||
|
const getTableColumns = computed(() => { |
||||
|
const columns: ColumnsType = [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
dataIndex: 'displayText', |
||||
|
fixed: 'left', |
||||
|
key: 'displayText', |
||||
|
title: $t('component.value_type_nput.type.SELECTION.displayText'), |
||||
|
width: 180, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
dataIndex: 'value', |
||||
|
fixed: 'left', |
||||
|
key: 'value', |
||||
|
title: $t('component.value_type_nput.type.SELECTION.value'), |
||||
|
width: 200, |
||||
|
}, |
||||
|
]; |
||||
|
if (!props.disabled) { |
||||
|
columns.push({ |
||||
|
align: 'center', |
||||
|
dataIndex: 'action', |
||||
|
fixed: 'right', |
||||
|
key: 'action', |
||||
|
title: $t('component.value_type_nput.type.SELECTION.actions.title'), |
||||
|
width: 220, |
||||
|
}); |
||||
|
} |
||||
|
return columns; |
||||
|
}); |
||||
|
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.some((item) => item.value === value)) { |
||||
|
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 'SELECTION': |
||||
|
case 'SelectionStringValueType': { |
||||
|
state.valueType = new SelectionStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
case 'TOGGLE': |
||||
|
case 'ToggleStringValueType': { |
||||
|
state.valueType = new ToggleStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
state.valueType = new FreeTextStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleValidatorChange(validator?: string) { |
||||
|
switch (validator) { |
||||
|
case 'BOOLEAN': { |
||||
|
state.valueType.validator = new BooleanValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case 'NULL': { |
||||
|
state.valueType.validator = new AlwaysValidValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case 'NUMERIC': { |
||||
|
state.valueType.validator = new NumericValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
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: Record<string, any>) { |
||||
|
state.selection.form.model = { |
||||
|
displayText: serialize(record.displayText), |
||||
|
value: record.value, |
||||
|
}; |
||||
|
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) { |
||||
|
const findIndex = items.findIndex( |
||||
|
(x) => serialize(x.displayText) === displayText, |
||||
|
); |
||||
|
items[findIndex] && |
||||
|
(items[findIndex].value = state.selection.form.model.value); |
||||
|
} else { |
||||
|
items.push({ |
||||
|
displayText: deserialize(displayText), |
||||
|
value: state.selection.form.model.value, |
||||
|
}); |
||||
|
} |
||||
|
form.resetFields(); |
||||
|
state.selection.modal.visible = false; |
||||
|
emits('change:selection', items); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record: Record<string, any>) { |
||||
|
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<StringValueTypeInstance>({ |
||||
|
validate, |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="container"> |
||||
|
<FormItemRest> |
||||
|
<Card> |
||||
|
<template #title> |
||||
|
<div class="type"> |
||||
|
<Row> |
||||
|
<Col :span="11"> |
||||
|
<span>{{ $t('component.value_type_nput.type.name') }}</span> |
||||
|
</Col> |
||||
|
<Col :span="2"> |
||||
|
<div class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<span>{{ |
||||
|
$t('component.value_type_nput.validator.name') |
||||
|
}}</span> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row> |
||||
|
<Col :span="11"> |
||||
|
<Select |
||||
|
:disabled="props.disabled" |
||||
|
:value="state.valueType.name" |
||||
|
@change="(value) => handleValueTypeChange(value?.toString())" |
||||
|
> |
||||
|
<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 class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<Select |
||||
|
:disabled="props.disabled" |
||||
|
:value="state.valueType.validator.name" |
||||
|
@change="(value) => handleValidatorChange(value?.toString())" |
||||
|
> |
||||
|
<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="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 class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<span>{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.validator.NUMERIC.maxValue', |
||||
|
) |
||||
|
}}</span> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row> |
||||
|
<Col :span="11"> |
||||
|
<InputNumber |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
v-model:value=" |
||||
|
(state.valueType.validator as NumericValueValidator) |
||||
|
.minValue |
||||
|
" |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col :span="2"> |
||||
|
<div class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<InputNumber |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
v-model:value=" |
||||
|
(state.valueType.validator as NumericValueValidator) |
||||
|
.maxValue |
||||
|
" |
||||
|
/> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</div> |
||||
|
<div |
||||
|
v-else-if="state.valueType.validator.name === 'STRING'" |
||||
|
class="string" |
||||
|
> |
||||
|
<Row style="margin-top: 10px"> |
||||
|
<Col :span="24"> |
||||
|
<Checkbox |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
v-model:checked=" |
||||
|
(state.valueType.validator as StringValueValidator) |
||||
|
.allowNull |
||||
|
" |
||||
|
> |
||||
|
{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.validator.STRING.allowNull', |
||||
|
) |
||||
|
}} |
||||
|
</Checkbox> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row style="margin-top: 10px"> |
||||
|
<Col :span="24"> |
||||
|
<span>{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.validator.STRING.regularExpression', |
||||
|
) |
||||
|
}}</span> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row> |
||||
|
<Col :span="24"> |
||||
|
<Input |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
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 class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<span>{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.validator.STRING.maxLength', |
||||
|
) |
||||
|
}}</span> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
<Row> |
||||
|
<Col :span="11"> |
||||
|
<InputNumber |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
v-model:value=" |
||||
|
(state.valueType.validator as StringValueValidator) |
||||
|
.minLength |
||||
|
" |
||||
|
/> |
||||
|
</Col> |
||||
|
<Col :span="2"> |
||||
|
<div class="w-full"></div> |
||||
|
</Col> |
||||
|
<Col :span="11"> |
||||
|
<InputNumber |
||||
|
:disabled="props.disabled" |
||||
|
class="w-full" |
||||
|
v-model:value=" |
||||
|
(state.valueType.validator as StringValueValidator) |
||||
|
.maxLength |
||||
|
" |
||||
|
/> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div |
||||
|
v-else-if="state.valueType.name === 'SelectionStringValueType'" |
||||
|
> |
||||
|
<Card class="selection"> |
||||
|
<template #title> |
||||
|
<Row> |
||||
|
<Col :span="12"> |
||||
|
<div |
||||
|
class="valid" |
||||
|
v-if=" |
||||
|
(state.valueType as SelectionStringValueType) |
||||
|
.itemSource.items.length <= 0 |
||||
|
" |
||||
|
> |
||||
|
<span>{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.type.SELECTION.itemsNotBeEmpty', |
||||
|
) |
||||
|
}}</span> |
||||
|
</div> |
||||
|
</Col> |
||||
|
<Col :span="12"> |
||||
|
<div class="toolbar" v-if="!props.disabled"> |
||||
|
<Button type="primary" @click="handleAddNew"> |
||||
|
{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.type.SELECTION.actions.create', |
||||
|
) |
||||
|
}} |
||||
|
</Button> |
||||
|
<Button danger @click="handleClean"> |
||||
|
{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.type.SELECTION.actions.clean', |
||||
|
) |
||||
|
}} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</Col> |
||||
|
</Row> |
||||
|
</template> |
||||
|
<Table |
||||
|
:columns="getTableColumns" |
||||
|
:data-source=" |
||||
|
(state.valueType as SelectionStringValueType).itemSource |
||||
|
.items |
||||
|
" |
||||
|
> |
||||
|
<template #bodyCell="{ column, record }"> |
||||
|
<template v-if="column.key === 'displayText'"> |
||||
|
<span>{{ getDisplayName(record.displayText) }}</span> |
||||
|
</template> |
||||
|
<template v-else-if="column.key === 'action'"> |
||||
|
<div class="flex flex-row items-center gap-1"> |
||||
|
<Button |
||||
|
v-if="props.allowEdit" |
||||
|
type="link" |
||||
|
@click="() => handleEdit(record)" |
||||
|
> |
||||
|
<template #icon> |
||||
|
<EditOutlined /> |
||||
|
</template> |
||||
|
{{ |
||||
|
$t( |
||||
|
'component.value_type_nput.type.SELECTION.actions.update', |
||||
|
) |
||||
|
}} |
||||
|
</Button> |
||||
|
<Button |
||||
|
v-if="props.allowDelete" |
||||
|
type="link" |
||||
|
@click="() => handleDelete(record)" |
||||
|
danger |
||||
|
> |
||||
|
<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="modal" v-bind="state.selection.modal"> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
class="form" |
||||
|
v-bind="state.selection.form" |
||||
|
:label-col="{ span: 6 }" |
||||
|
:wrapper-col="{ span: 18 }" |
||||
|
> |
||||
|
<FormItem |
||||
|
name="displayText" |
||||
|
required |
||||
|
:label="$t('component.value_type_nput.type.SELECTION.displayText')" |
||||
|
> |
||||
|
<LocalizableInput |
||||
|
:disabled="props.disabled || state.selection.form.editFlag" |
||||
|
v-model:value="state.selection.form.model.displayText" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem |
||||
|
name="value" |
||||
|
required |
||||
|
:label="$t('component.value_type_nput.type.SELECTION.value')" |
||||
|
> |
||||
|
<Input |
||||
|
:disabled="props.disabled" |
||||
|
v-model:value="state.selection.form.model.value" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.container { |
||||
|
.type { |
||||
|
width: 100% !important; |
||||
|
min-height: 70px; |
||||
|
} |
||||
|
|
||||
|
.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,4 @@ |
|||||
|
export * from './interface'; |
||||
|
export { default as StringValueTypeInput } from './StringValueTypeInput.vue'; |
||||
|
export * from './validator'; |
||||
|
export * from './valueType'; |
||||
@ -0,0 +1,3 @@ |
|||||
|
export interface StringValueTypeInstance { |
||||
|
validate(value: any): Promise<any>; |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
import type { Dictionary } from '@abp/core'; |
||||
|
|
||||
|
import { isBoolean, isNumber } from '@vben/utils'; |
||||
|
|
||||
|
import { isNullOrUnDef, isNullOrWhiteSpace } from '@abp/core'; |
||||
|
|
||||
|
export interface ValueValidator { |
||||
|
isValid(value?: any): boolean; |
||||
|
name: string; |
||||
|
|
||||
|
properties: Dictionary<string, any>; |
||||
|
} |
||||
|
|
||||
|
export class AlwaysValidValueValidator implements ValueValidator { |
||||
|
name = 'NULL'; |
||||
|
properties: Dictionary<string, any>; |
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
isValid(_value?: any): boolean { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class BooleanValueValidator implements ValueValidator { |
||||
|
name = 'BOOLEAN'; |
||||
|
properties: Dictionary<string, any>; |
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
isValid(value?: any): boolean { |
||||
|
if (isNullOrUnDef(value)) return true; |
||||
|
if (isBoolean(value)) return true; |
||||
|
const bolString = String(value).toLowerCase(); |
||||
|
if (bolString === 'true' || bolString === 'false') return true; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class NumericValueValidator implements ValueValidator { |
||||
|
name = 'NUMERIC'; |
||||
|
properties: Dictionary<string, any>; |
||||
|
get maxValue(): number | undefined { |
||||
|
return Number(this.properties.MaxValue); |
||||
|
} |
||||
|
|
||||
|
set maxValue(value: number) { |
||||
|
this.properties.MaxValue = value; |
||||
|
} |
||||
|
|
||||
|
get minValue(): number | undefined { |
||||
|
return Number(this.properties.MinValue); |
||||
|
} |
||||
|
|
||||
|
set minValue(value: number) { |
||||
|
this.properties.MinValue = value; |
||||
|
} |
||||
|
|
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
|
||||
|
_isValidInternal(value: number): boolean { |
||||
|
if (this.minValue && value < this.minValue) return false; |
||||
|
if (this.maxValue && value > this.maxValue) return false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
isValid(value?: any): boolean { |
||||
|
if (isNullOrUnDef(value)) return true; |
||||
|
if (isNumber(value)) return this._isValidInternal(value); |
||||
|
const numString = String(value); |
||||
|
if (!isNullOrUnDef(numString)) { |
||||
|
const num = Number(numString); |
||||
|
if (num) return this._isValidInternal(num); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class StringValueValidator implements ValueValidator { |
||||
|
name = 'STRING'; |
||||
|
properties: Dictionary<string, any>; |
||||
|
get allowNull(): boolean { |
||||
|
return ( |
||||
|
String(this.properties.AllowNull ?? 'true')?.toLowerCase() === 'true' |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
set allowNull(value: boolean) { |
||||
|
this.properties.AllowNull = value; |
||||
|
} |
||||
|
|
||||
|
get maxLength(): number | undefined { |
||||
|
return Number(this.properties.MaxLength); |
||||
|
} |
||||
|
|
||||
|
set maxLength(value: number) { |
||||
|
this.properties.MaxLength = value; |
||||
|
} |
||||
|
|
||||
|
get minLength(): number | undefined { |
||||
|
return Number(this.properties.MinLength); |
||||
|
} |
||||
|
|
||||
|
set minLength(value: number) { |
||||
|
this.properties.MinLength = value; |
||||
|
} |
||||
|
|
||||
|
get regularExpression(): string { |
||||
|
return String(this.properties.RegularExpression ?? ''); |
||||
|
} |
||||
|
|
||||
|
set regularExpression(value: string) { |
||||
|
this.properties.RegularExpression = value; |
||||
|
} |
||||
|
|
||||
|
constructor() { |
||||
|
this.properties = {}; |
||||
|
} |
||||
|
|
||||
|
isValid(value?: any): boolean { |
||||
|
if (!this.allowNull && isNullOrUnDef(value)) return false; |
||||
|
const valueString = String(value); |
||||
|
if (!this.allowNull && isNullOrWhiteSpace(valueString.trim())) return false; |
||||
|
if ( |
||||
|
this.minLength && |
||||
|
this.minLength > 0 && |
||||
|
valueString.length < this.minLength |
||||
|
) |
||||
|
return false; |
||||
|
if ( |
||||
|
this.maxLength && |
||||
|
this.maxLength > 0 && |
||||
|
valueString.length > this.maxLength |
||||
|
) |
||||
|
return false; |
||||
|
if (!isNullOrWhiteSpace(this.regularExpression)) { |
||||
|
return new RegExp(this.regularExpression).test(valueString); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,120 @@ |
|||||
|
import type { Dictionary, LocalizableStringInfo } from '@abp/core'; |
||||
|
|
||||
|
import type { ValueValidator } from './validator'; |
||||
|
|
||||
|
import { |
||||
|
AlwaysValidValueValidator, |
||||
|
BooleanValueValidator, |
||||
|
NumericValueValidator, |
||||
|
StringValueValidator, |
||||
|
} from './validator'; |
||||
|
|
||||
|
export interface StringValueType { |
||||
|
name: string; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
} |
||||
|
|
||||
|
export interface SelectionStringValueItem { |
||||
|
displayText: LocalizableStringInfo; |
||||
|
value: string; |
||||
|
} |
||||
|
|
||||
|
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 { |
||||
|
itemSource: SelectionStringValueItemSource; |
||||
|
name = 'SelectionStringValueType'; |
||||
|
properties: Dictionary<string, any>; |
||||
|
validator: ValueValidator; |
||||
|
constructor(validator?: ValueValidator) { |
||||
|
this.properties = {}; |
||||
|
this.itemSource = { |
||||
|
items: [], |
||||
|
}; |
||||
|
this.validator = validator ?? new AlwaysValidValueValidator(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class StringValueTypeSerializer { |
||||
|
_deserializeValidator(validator: any): ValueValidator { |
||||
|
let convertValidator: ValueValidator = new AlwaysValidValueValidator(); |
||||
|
if (validator.name) { |
||||
|
switch (validator.name) { |
||||
|
case 'BOOLEAN': { |
||||
|
convertValidator = new BooleanValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case 'NULL': { |
||||
|
convertValidator = new AlwaysValidValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case 'NUMERIC': { |
||||
|
convertValidator = new NumericValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
case 'STRING': { |
||||
|
convertValidator = new StringValueValidator(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
convertValidator.properties = validator.properties; |
||||
|
return convertValidator; |
||||
|
} |
||||
|
|
||||
|
deserialize(value: string): StringValueType { |
||||
|
let valueType: StringValueType; |
||||
|
const valueTypeObj = JSON.parse(value); |
||||
|
switch (valueTypeObj.name) { |
||||
|
case 'SELECTION': |
||||
|
case 'SelectionStringValueType': { |
||||
|
valueType = new SelectionStringValueType(); |
||||
|
(valueType as SelectionStringValueType).itemSource = |
||||
|
valueTypeObj.itemSource; |
||||
|
break; |
||||
|
} |
||||
|
case 'TOGGLE': |
||||
|
case 'ToggleStringValueType': { |
||||
|
valueType = new ToggleStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
default: { |
||||
|
valueType = new FreeTextStringValueType(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
valueType.properties = valueTypeObj.properties; |
||||
|
valueType.validator = this._deserializeValidator(valueTypeObj.validator); |
||||
|
return valueType; |
||||
|
} |
||||
|
|
||||
|
serialize(value: StringValueType): string { |
||||
|
const valueTypeString = JSON.stringify(value); |
||||
|
return valueTypeString; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const valueTypeSerializer = new StringValueTypeSerializer(); |
||||
Loading…
Reference in new issue