初始代码
This commit is contained in:
9
src/components/Form/index.ts
Normal file
9
src/components/Form/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import BasicForm from './src/BasicForm.vue';
|
||||
|
||||
export * from './src/types/form';
|
||||
export * from './src/types/formItem';
|
||||
|
||||
export { useComponentRegister } from './src/hooks/useComponentRegister';
|
||||
export { useForm } from './src/hooks/useForm';
|
||||
|
||||
export { BasicForm };
|
||||
358
src/components/Form/src/BasicForm.vue
Normal file
358
src/components/Form/src/BasicForm.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<Form v-bind="getBindValue" :class="getFormClass" ref="formElRef" :model="formModel" @keypress.enter="handleEnterPress" :name="getFormName">
|
||||
<Row v-bind="getRow">
|
||||
<slot name="formHeader"></slot>
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<FormItem
|
||||
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
|
||||
:tableAction="tableAction"
|
||||
:formActionType="formActionType"
|
||||
:schema="schema"
|
||||
:formProps="getProps"
|
||||
:allDefaultValues="defaultValueRef"
|
||||
:formModel="formModel"
|
||||
:setFormModel="setFormModel">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
|
||||
<template #[item]="data" v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</FormAction>
|
||||
<slot name="formFooter"></slot>
|
||||
</Row>
|
||||
</Form>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { FormActionType, FormProps, FormSchema } from './types/form';
|
||||
import type { AdvanceState } from './types/hooks';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
|
||||
import { Form, Row } from 'ant-design-vue';
|
||||
import FormItem from './components/FormItem.vue';
|
||||
import FormAction from './components/FormAction.vue';
|
||||
|
||||
// import { cloneDeep } from 'lodash-es';
|
||||
import { deepMerge } from '@/utils';
|
||||
|
||||
import { useFormValues } from './hooks/useFormValues';
|
||||
import useAdvanced from './hooks/useAdvanced';
|
||||
import { useFormEvents } from './hooks/useFormEvents';
|
||||
import { createFormContext } from './hooks/useFormContext';
|
||||
import { useAutoFocus } from './hooks/useAutoFocus';
|
||||
import { useModalContext } from '@/components/Modal';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
import { basicProps } from './props';
|
||||
import { useDesign } from '@/hooks/web/useDesign';
|
||||
import { buildUUID } from '@/utils/uuid';
|
||||
import { isFunction, isArray } from '@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicForm',
|
||||
components: { FormItem, Form, Row, FormAction },
|
||||
props: basicProps,
|
||||
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
|
||||
setup(props, { emit, attrs }) {
|
||||
const formModel = reactive<Recordable>({});
|
||||
const modalFn = useModalContext();
|
||||
|
||||
const advanceState = reactive<AdvanceState>({
|
||||
// 默认是收起状态
|
||||
isAdvanced: false,
|
||||
hideAdvanceBtn: false,
|
||||
isLoad: false,
|
||||
actionSpan: 6,
|
||||
});
|
||||
|
||||
const defaultValueRef = ref<Recordable>({});
|
||||
const fullValueRef = ref<Recordable>({});
|
||||
const isInitedDefaultRef = ref(false);
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||
const formElRef = ref<Nullable<FormActionType>>(null);
|
||||
|
||||
const { prefixCls } = useDesign('basic-form');
|
||||
|
||||
// 每个表单生成不同name保证id不重复
|
||||
const getFormName = computed((): string => {
|
||||
return `form-${buildUUID()}`;
|
||||
});
|
||||
// Get the basic configuration of the form
|
||||
const getProps = computed((): FormProps => {
|
||||
const newProps: any = unref(propsRef);
|
||||
return { ...props, ...newProps } as FormProps;
|
||||
});
|
||||
|
||||
const getFormClass = computed(() => {
|
||||
return [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}--compact`]: unref(getProps).compact,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// Get uniform row style and Row configuration for the entire form
|
||||
const getRow = computed((): Recordable => {
|
||||
const { baseRowStyle = {}, rowProps } = unref(getProps);
|
||||
return {
|
||||
style: baseRowStyle,
|
||||
gutter: 16,
|
||||
...rowProps,
|
||||
};
|
||||
});
|
||||
|
||||
const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable));
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||
if (unref(getProps).showAdvancedButton) {
|
||||
return schemas.filter(schema => schema.component !== 'Divider') as FormSchema[];
|
||||
} else {
|
||||
return schemas as FormSchema[];
|
||||
}
|
||||
});
|
||||
|
||||
const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
|
||||
advanceState,
|
||||
emit,
|
||||
getProps,
|
||||
getSchema,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
});
|
||||
|
||||
const { handleFormValues, initDefault } = useFormValues({
|
||||
getProps,
|
||||
defaultValueRef,
|
||||
getSchema,
|
||||
formModel,
|
||||
});
|
||||
|
||||
useAutoFocus({
|
||||
getSchema,
|
||||
getProps,
|
||||
isInitedDefault: isInitedDefaultRef,
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
setFieldsValue,
|
||||
clearValidate,
|
||||
validate,
|
||||
validateFields,
|
||||
getFieldsValue,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
appendSchemaByField,
|
||||
removeSchemaByField,
|
||||
resetFields,
|
||||
scrollToField,
|
||||
} = useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
defaultValueRef,
|
||||
fullValueRef,
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
schemaRef: schemaRef as Ref<FormSchema[]>,
|
||||
handleFormValues,
|
||||
isInitedDefaultRef,
|
||||
});
|
||||
|
||||
createFormContext({
|
||||
resetAction: resetFields,
|
||||
submitAction: handleSubmit,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(getProps).model,
|
||||
() => {
|
||||
const { model } = unref(getProps);
|
||||
if (!model) return;
|
||||
setFieldsValue(model);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => unref(getProps).schemas,
|
||||
schemas => {
|
||||
resetSchema(schemas ?? []);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => getSchema.value,
|
||||
schema => {
|
||||
nextTick(() => {
|
||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||
modalFn?.redoModalHeight?.();
|
||||
});
|
||||
if (unref(isInitedDefaultRef)) {
|
||||
return;
|
||||
}
|
||||
if (schema?.length) {
|
||||
initDefault();
|
||||
isInitedDefaultRef.value = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => formModel,
|
||||
useDebounceFn(() => {
|
||||
unref(getProps).submitOnChange && handleSubmit();
|
||||
}, 300),
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||
}
|
||||
|
||||
function setFormModel(key: string, value: any, schema: FormSchema) {
|
||||
formModel[key] = value;
|
||||
const { validateTrigger } = unref(getBindValue);
|
||||
if (isFunction(schema.dynamicRules) || isArray(schema.rules)) {
|
||||
return;
|
||||
}
|
||||
if (!validateTrigger || validateTrigger === 'change') {
|
||||
validateFields([key]).catch(_ => {});
|
||||
}
|
||||
emit('field-value-change', key, value);
|
||||
}
|
||||
|
||||
function handleEnterPress(e: KeyboardEvent) {
|
||||
const { autoSubmitOnEnter } = unref(getProps);
|
||||
if (!autoSubmitOnEnter) return;
|
||||
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
|
||||
const target: HTMLElement = e.target as HTMLElement;
|
||||
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
|
||||
handleSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formActionType: Partial<FormActionType> = {
|
||||
getFieldsValue,
|
||||
setFieldsValue,
|
||||
resetFields,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
setProps,
|
||||
removeSchemaByField,
|
||||
appendSchemaByField,
|
||||
clearValidate,
|
||||
validateFields,
|
||||
validate,
|
||||
submit: handleSubmit,
|
||||
scrollToField: scrollToField,
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initDefault();
|
||||
emit('register', formActionType);
|
||||
});
|
||||
|
||||
return {
|
||||
getBindValue,
|
||||
handleToggleAdvanced,
|
||||
handleEnterPress,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
advanceState,
|
||||
getRow,
|
||||
getProps,
|
||||
formElRef,
|
||||
getSchema,
|
||||
formActionType: formActionType as any,
|
||||
setFormModel,
|
||||
getFormClass,
|
||||
getFormActionBindProps: computed((): Recordable => ({ ...getProps.value, ...advanceState })),
|
||||
fieldsIsAdvancedMap,
|
||||
...formActionType,
|
||||
getFormName,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-form';
|
||||
|
||||
.@{prefix-cls} {
|
||||
.ant-form-item {
|
||||
// &-label label::after {
|
||||
// margin: 0 6px 0 2px;
|
||||
// }
|
||||
|
||||
&-with-help {
|
||||
margin-bottom: 0;
|
||||
.ant-form-item-explain {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
min-height: 20px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.ant-form-item-with-help) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&.suffix-item {
|
||||
.ant-form-item-children {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-form-item-control {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
display: inline-flex;
|
||||
padding-left: 6px;
|
||||
margin-top: 1px;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item-explain {
|
||||
height: 0;
|
||||
}
|
||||
.ant-form-item-extra {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
min-height: 20px !important;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
.ant-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
}
|
||||
&.search-form {
|
||||
.ant-form-item {
|
||||
display: flex;
|
||||
.ant-form-item-row {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
.ant-form-item-label {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
src/components/Form/src/componentMap.ts
Normal file
150
src/components/Form/src/componentMap.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { Component } from 'vue';
|
||||
import type { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
* Component list, register here to setting it in the form
|
||||
*/
|
||||
import { StrengthMeter } from '@/components/StrengthMeter';
|
||||
import { CountdownInput } from '@/components/CountDown';
|
||||
// yunzhupaas 组件
|
||||
import {
|
||||
YunzhupaasAlert,
|
||||
YunzhupaasAreaSelect,
|
||||
YunzhupaasAutoComplete,
|
||||
YunzhupaasButton,
|
||||
YunzhupaasCron,
|
||||
YunzhupaasCascader,
|
||||
YunzhupaasColorPicker,
|
||||
YunzhupaasCheckbox,
|
||||
YunzhupaasCheckboxSingle,
|
||||
YunzhupaasDatePicker,
|
||||
YunzhupaasDateRange,
|
||||
YunzhupaasTimePicker,
|
||||
YunzhupaasTimeRange,
|
||||
YunzhupaasMonthPicker,
|
||||
YunzhupaasWeekPicker,
|
||||
YunzhupaasDivider,
|
||||
YunzhupaasEditor,
|
||||
YunzhupaasGroupTitle,
|
||||
YunzhupaasIconPicker,
|
||||
YunzhupaasInput,
|
||||
YunzhupaasInputPassword,
|
||||
YunzhupaasInputGroup,
|
||||
YunzhupaasInputSearch,
|
||||
YunzhupaasTextarea,
|
||||
YunzhupaasInputNumber,
|
||||
YunzhupaasLink,
|
||||
YunzhupaasOpenData,
|
||||
YunzhupaasOrganizeSelect,
|
||||
YunzhupaasDepSelect,
|
||||
YunzhupaasPosSelect,
|
||||
YunzhupaasGroupSelect,
|
||||
YunzhupaasRoleSelect,
|
||||
YunzhupaasUserSelect,
|
||||
YunzhupaasUsersSelect,
|
||||
YunzhupaasQrcode,
|
||||
YunzhupaasBarcode,
|
||||
YunzhupaasRadio,
|
||||
YunzhupaasRate,
|
||||
YunzhupaasSelect,
|
||||
YunzhupaasSlider,
|
||||
YunzhupaasSign,
|
||||
YunzhupaasSignature,
|
||||
YunzhupaasSwitch,
|
||||
YunzhupaasText,
|
||||
YunzhupaasTreeSelect,
|
||||
YunzhupaasUploadFile,
|
||||
YunzhupaasUploadImg,
|
||||
YunzhupaasUploadImgSingle,
|
||||
YunzhupaasRelationForm,
|
||||
YunzhupaasRelationFormAttr,
|
||||
YunzhupaasPopupSelect,
|
||||
YunzhupaasPopupTableSelect,
|
||||
YunzhupaasPopupAttr,
|
||||
YunzhupaasNumberRange,
|
||||
YunzhupaasCalculate,
|
||||
YunzhupaasInputTable,
|
||||
YunzhupaasLocation,
|
||||
YunzhupaasIframe,
|
||||
} from '@/components/Yunzhupaas';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('StrengthMeter', StrengthMeter);
|
||||
componentMap.set('InputCountDown', CountdownInput);
|
||||
|
||||
componentMap.set('InputGroup', YunzhupaasInputGroup);
|
||||
componentMap.set('InputSearch', YunzhupaasInputSearch);
|
||||
componentMap.set('MonthPicker', YunzhupaasMonthPicker);
|
||||
componentMap.set('WeekPicker', YunzhupaasWeekPicker);
|
||||
|
||||
componentMap.set('Alert', YunzhupaasAlert);
|
||||
componentMap.set('AreaSelect', YunzhupaasAreaSelect);
|
||||
componentMap.set('AutoComplete', YunzhupaasAutoComplete);
|
||||
componentMap.set('Button', YunzhupaasButton);
|
||||
componentMap.set('Cron', YunzhupaasCron);
|
||||
componentMap.set('Cascader', YunzhupaasCascader);
|
||||
componentMap.set('ColorPicker', YunzhupaasColorPicker);
|
||||
componentMap.set('Checkbox', YunzhupaasCheckbox);
|
||||
componentMap.set('YunzhupaasCheckboxSingle', YunzhupaasCheckboxSingle);
|
||||
componentMap.set('DatePicker', YunzhupaasDatePicker);
|
||||
componentMap.set('DateRange', YunzhupaasDateRange);
|
||||
componentMap.set('TimePicker', YunzhupaasTimePicker);
|
||||
componentMap.set('TimeRange', YunzhupaasTimeRange);
|
||||
componentMap.set('Divider', YunzhupaasDivider);
|
||||
componentMap.set('Editor', YunzhupaasEditor);
|
||||
componentMap.set('GroupTitle', YunzhupaasGroupTitle);
|
||||
componentMap.set('Input', YunzhupaasInput);
|
||||
componentMap.set('InputPassword', YunzhupaasInputPassword);
|
||||
componentMap.set('Textarea', YunzhupaasTextarea);
|
||||
componentMap.set('InputNumber', YunzhupaasInputNumber);
|
||||
componentMap.set('IconPicker', YunzhupaasIconPicker);
|
||||
componentMap.set('Link', YunzhupaasLink);
|
||||
componentMap.set('OrganizeSelect', YunzhupaasOrganizeSelect);
|
||||
componentMap.set('DepSelect', YunzhupaasDepSelect);
|
||||
componentMap.set('PosSelect', YunzhupaasPosSelect);
|
||||
componentMap.set('GroupSelect', YunzhupaasGroupSelect);
|
||||
componentMap.set('RoleSelect', YunzhupaasRoleSelect);
|
||||
componentMap.set('UserSelect', YunzhupaasUserSelect);
|
||||
componentMap.set('UsersSelect', YunzhupaasUsersSelect);
|
||||
componentMap.set('Qrcode', YunzhupaasQrcode);
|
||||
componentMap.set('Barcode', YunzhupaasBarcode);
|
||||
componentMap.set('Radio', YunzhupaasRadio);
|
||||
componentMap.set('Rate', YunzhupaasRate);
|
||||
componentMap.set('Select', YunzhupaasSelect);
|
||||
componentMap.set('Slider', YunzhupaasSlider);
|
||||
componentMap.set('Sign', YunzhupaasSign);
|
||||
componentMap.set('Signature', YunzhupaasSignature);
|
||||
componentMap.set('Switch', YunzhupaasSwitch);
|
||||
componentMap.set('Text', YunzhupaasText);
|
||||
componentMap.set('TreeSelect', YunzhupaasTreeSelect);
|
||||
componentMap.set('UploadFile', YunzhupaasUploadFile);
|
||||
componentMap.set('UploadImg', YunzhupaasUploadImg);
|
||||
componentMap.set('UploadImgSingle', YunzhupaasUploadImgSingle);
|
||||
componentMap.set('BillRule', YunzhupaasInput);
|
||||
componentMap.set('ModifyUser', YunzhupaasInput);
|
||||
componentMap.set('ModifyTime', YunzhupaasInput);
|
||||
componentMap.set('CreateUser', YunzhupaasOpenData);
|
||||
componentMap.set('CreateTime', YunzhupaasOpenData);
|
||||
componentMap.set('CurrOrganize', YunzhupaasOpenData);
|
||||
componentMap.set('CurrPosition', YunzhupaasOpenData);
|
||||
componentMap.set('RelationForm', YunzhupaasRelationForm);
|
||||
componentMap.set('RelationFormAttr', YunzhupaasRelationFormAttr);
|
||||
componentMap.set('PopupSelect', YunzhupaasPopupSelect);
|
||||
componentMap.set('PopupTableSelect', YunzhupaasPopupTableSelect);
|
||||
componentMap.set('PopupAttr', YunzhupaasPopupAttr);
|
||||
componentMap.set('NumberRange', YunzhupaasNumberRange);
|
||||
componentMap.set('Calculate', YunzhupaasCalculate);
|
||||
componentMap.set('InputTable', YunzhupaasInputTable);
|
||||
componentMap.set('Location', YunzhupaasLocation);
|
||||
componentMap.set('Iframe', YunzhupaasIframe);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
114
src/components/Form/src/components/FormAction.vue
Normal file
114
src/components/Form/src/components/FormAction.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
|
||||
<div class="w-full" :style="{ textAlign: actionColOpt.style.textAlign }">
|
||||
<FormItem>
|
||||
<slot name="submitBefore"></slot>
|
||||
<Button type="primary" class="mr-2" v-bind="getSubmitBtnOptions" @click="submitAction" v-if="showSubmitButton">
|
||||
{{ getSubmitBtnOptions.text }}
|
||||
</Button>
|
||||
<slot name="resetBefore"></slot>
|
||||
<Button type="default" class="mr-2" v-bind="getResetBtnOptions" @click="resetAction" v-if="showResetButton">
|
||||
{{ getResetBtnOptions.text }}
|
||||
</Button>
|
||||
<slot name="advanceBefore"></slot>
|
||||
<Button type="link" size="small" @click="toggleAdvanced" v-if="showAdvancedButton && !hideAdvanceBtn">
|
||||
{{ isAdvanced ? t('component.form.fold') : t('component.form.unfold') }}
|
||||
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
|
||||
</Button>
|
||||
<slot name="advanceAfter"></slot>
|
||||
</FormItem>
|
||||
</div>
|
||||
</a-col>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { ColEx } from '../types/index';
|
||||
//import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import { Button, ButtonProps } from '@/components/Button';
|
||||
import { BasicArrow } from '@/components/Basic';
|
||||
import { useFormContext } from '../hooks/useFormContext';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
|
||||
type ButtonOptions = Partial<ButtonProps> & { text: string };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormAction',
|
||||
components: {
|
||||
FormItem: Form.Item,
|
||||
Button,
|
||||
BasicArrow,
|
||||
[Col.name]: Col,
|
||||
},
|
||||
props: {
|
||||
showActionButtonGroup: propTypes.bool.def(true),
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
showAdvancedButton: propTypes.bool.def(true),
|
||||
resetButtonOptions: {
|
||||
type: Object as PropType<ButtonOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
submitButtonOptions: {
|
||||
type: Object as PropType<ButtonOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
actionColOptions: {
|
||||
type: Object as PropType<Partial<ColEx>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
actionSpan: propTypes.number.def(6),
|
||||
isAdvanced: propTypes.bool,
|
||||
hideAdvanceBtn: propTypes.bool,
|
||||
},
|
||||
emits: ['toggle-advanced'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const actionColOpt = computed(() => {
|
||||
const { showAdvancedButton, actionSpan: span, actionColOptions } = props;
|
||||
const actionSpan = 24 - span;
|
||||
const advancedSpanObj = showAdvancedButton ? { span: actionSpan < 6 ? 24 : actionSpan } : {};
|
||||
const actionColOpt: Partial<ColEx> = {
|
||||
style: { textAlign: 'left' },
|
||||
span: showAdvancedButton ? 6 : 4,
|
||||
...advancedSpanObj,
|
||||
...actionColOptions,
|
||||
};
|
||||
return actionColOpt;
|
||||
});
|
||||
|
||||
const getResetBtnOptions = computed((): ButtonOptions => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('common.resetText'),
|
||||
},
|
||||
props.resetButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
const getSubmitBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('common.queryText'),
|
||||
},
|
||||
props.submitButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
function toggleAdvanced() {
|
||||
emit('toggle-advanced');
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
actionColOpt,
|
||||
getResetBtnOptions,
|
||||
getSubmitBtnOptions,
|
||||
toggleAdvanced,
|
||||
...useFormContext(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
383
src/components/Form/src/components/FormItem.vue
Normal file
383
src/components/Form/src/components/FormItem.vue
Normal file
@@ -0,0 +1,383 @@
|
||||
<script lang="tsx">
|
||||
import type { PropType, Ref } from 'vue';
|
||||
import { computed, defineComponent, toRefs, unref } from 'vue';
|
||||
import type { FormActionType, FormProps, FormSchema } from '../types/form';
|
||||
import type { Rule } from 'ant-design-vue/es/Form';
|
||||
import type { TableActionType } from '@/components/Table';
|
||||
import { Col, Form } from 'ant-design-vue';
|
||||
import { YunzhupaasDivider } from '@/components/Yunzhupaas';
|
||||
import { componentMap } from '../componentMap';
|
||||
import { isBoolean, isFunction, isNull, isArray } from '@/utils/is';
|
||||
import { getSlot } from '@/utils/helper/tsxHelper';
|
||||
import { createPlaceholderMessage, setComponentRuleType, noFieldComponents, numberItemType, useInputComponents } from '../helper';
|
||||
import { cloneDeep, upperFirst } from 'lodash-es';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { useItemLabelWidth } from '../hooks/useLabelWidth';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormItem',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
schema: {
|
||||
type: Object as PropType<FormSchema>,
|
||||
default: () => ({}),
|
||||
},
|
||||
formProps: {
|
||||
type: Object as PropType<FormProps>,
|
||||
default: () => ({}),
|
||||
},
|
||||
allDefaultValues: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
setFormModel: {
|
||||
type: Function as PropType<(key: string, value: any, schema: FormSchema) => void>,
|
||||
default: null,
|
||||
},
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
formActionType: {
|
||||
type: Object as PropType<FormActionType>,
|
||||
},
|
||||
isAdvanced: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const { t } = useI18n();
|
||||
const { hasFormP } = usePermission();
|
||||
|
||||
const { schema, formProps } = toRefs(props) as {
|
||||
schema: Ref<FormSchema>;
|
||||
formProps: Ref<FormProps>;
|
||||
};
|
||||
|
||||
const itemLabelWidthProp = useItemLabelWidth(schema, formProps);
|
||||
|
||||
const getValues = computed(() => {
|
||||
const { allDefaultValues, formModel, schema } = props;
|
||||
const { mergeDynamicData } = props.formProps;
|
||||
return {
|
||||
field: schema.field,
|
||||
model: formModel,
|
||||
values: {
|
||||
...mergeDynamicData,
|
||||
...allDefaultValues,
|
||||
...formModel,
|
||||
} as Recordable,
|
||||
schema: schema,
|
||||
};
|
||||
});
|
||||
|
||||
const getComponentsProps = computed(() => {
|
||||
const { schema, tableAction, formModel, formActionType } = props;
|
||||
let { componentProps = {} } = schema;
|
||||
if (isFunction(componentProps)) {
|
||||
componentProps = componentProps({ schema, tableAction, formModel, formActionType }) ?? {};
|
||||
}
|
||||
return componentProps as Recordable;
|
||||
});
|
||||
|
||||
const getDisable = computed(() => {
|
||||
const { disabled: globDisabled } = props.formProps;
|
||||
const { dynamicDisabled } = props.schema;
|
||||
const { disabled: itemDisabled = false } = unref(getComponentsProps);
|
||||
let disabled = !!globDisabled || itemDisabled;
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled;
|
||||
}
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled(unref(getValues));
|
||||
}
|
||||
return disabled;
|
||||
});
|
||||
|
||||
function getShow(): { isShow: boolean; isIfShow: boolean } {
|
||||
const { show, ifShow, auth = '' } = props.schema;
|
||||
const { showAdvancedButton } = props.formProps;
|
||||
const itemIsAdvanced = showAdvancedButton ? (isBoolean(props.isAdvanced) ? props.isAdvanced : true) : true;
|
||||
|
||||
let isShow = true;
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show(unref(getValues));
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(unref(getValues));
|
||||
}
|
||||
isShow = isShow && itemIsAdvanced;
|
||||
if (auth) isIfShow = !hasFormP(auth) ? hasFormP(auth) : isIfShow;
|
||||
return { isShow, isIfShow };
|
||||
}
|
||||
|
||||
function handleRules(): Rule[] {
|
||||
const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required } = props.schema;
|
||||
|
||||
if (isFunction(dynamicRules)) {
|
||||
return dynamicRules(unref(getValues)) as Rule[];
|
||||
}
|
||||
|
||||
let rules: Rule[] = cloneDeep(defRules) as Rule[];
|
||||
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
|
||||
|
||||
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') ? rulesMessageJoinLabel : globalRulesMessageJoinLabel;
|
||||
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
|
||||
|
||||
function validator(rule: any, value: any) {
|
||||
const msg = rule.message || defaultMsg;
|
||||
if (value === undefined || isNull(value)) {
|
||||
// 空值
|
||||
return Promise.reject(msg);
|
||||
} else if (Array.isArray(value) && value.length === 0) {
|
||||
// 数组类型
|
||||
return Promise.reject(msg);
|
||||
} else if (typeof value === 'string' && value.trim() === '') {
|
||||
// 空字符串
|
||||
return Promise.reject(msg);
|
||||
} else if (
|
||||
typeof value === 'object' &&
|
||||
Reflect.has(value, 'checked') &&
|
||||
Reflect.has(value, 'halfChecked') &&
|
||||
Array.isArray(value.checked) &&
|
||||
Array.isArray(value.halfChecked) &&
|
||||
value.checked.length === 0 &&
|
||||
value.halfChecked.length === 0
|
||||
) {
|
||||
// 非关联选择的tree组件
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const getRequired = isFunction(required) ? required(unref(getValues)) : required;
|
||||
|
||||
/*
|
||||
* 1、若设置了required属性,又没有其他的rules,就创建一个验证规则;
|
||||
* 2、若设置了required属性,又存在其他的rules,则只rules中不存在required属性时,才添加验证required的规则
|
||||
* 也就是说rules中的required,优先级大于required
|
||||
*/
|
||||
if (getRequired) {
|
||||
if (!rules || rules.length === 0) {
|
||||
rules = [{ required: getRequired, validator }];
|
||||
} else {
|
||||
const requiredIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required'));
|
||||
|
||||
if (requiredIndex === -1) {
|
||||
rules.push({ required: getRequired, validator });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const requiredRuleIndex: number = rules.findIndex(rule => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'));
|
||||
|
||||
if (requiredRuleIndex !== -1) {
|
||||
const rule = rules[requiredRuleIndex];
|
||||
const { isShow } = getShow();
|
||||
if (!isShow) {
|
||||
rule.required = false;
|
||||
}
|
||||
if (component) {
|
||||
if (!Reflect.has(rule, 'type')) {
|
||||
rule.type = component === 'InputNumber' ? 'number' : 'string';
|
||||
}
|
||||
|
||||
rule.message = rule.message || defaultMsg;
|
||||
|
||||
if (component.includes('Input') || component.includes('Textarea')) {
|
||||
rule.whitespace = true;
|
||||
}
|
||||
const valueFormat = unref(getComponentsProps)?.valueFormat;
|
||||
setComponentRuleType(rule, component, valueFormat);
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum input length rule check
|
||||
const characterInx = rules.findIndex(val => val.max);
|
||||
if (characterInx !== -1 && !rules[characterInx].validator) {
|
||||
rules[characterInx].message = rules[characterInx].message || t('component.form.maxTip', [rules[characterInx].max] as Recordable);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
function renderComponent() {
|
||||
const { renderComponentContent, component, field, changeEvent = 'change', valueField } = props.schema;
|
||||
|
||||
if (useInputComponents.includes(component)) {
|
||||
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
|
||||
return <Comp readonly={true} placeholder="系统自动生成" />;
|
||||
}
|
||||
|
||||
const eventKey = `on${upperFirst(changeEvent)}`;
|
||||
|
||||
const on = {
|
||||
[eventKey]: (...args: Nullable<Recordable>[]) => {
|
||||
const [e] = args;
|
||||
if (propsData[eventKey]) {
|
||||
propsData[eventKey](...args);
|
||||
}
|
||||
const target = e ? e.target : null;
|
||||
const value = target ? target.value : e;
|
||||
props.setFormModel(field, value, props.schema);
|
||||
},
|
||||
};
|
||||
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;
|
||||
|
||||
const { autoSetPlaceHolder, size } = props.formProps;
|
||||
const propsData: Recordable = {
|
||||
allowClear: true,
|
||||
// getPopupContainer: (trigger: Element) => trigger.parentNode,
|
||||
size,
|
||||
...unref(getComponentsProps),
|
||||
disabled: unref(getDisable),
|
||||
};
|
||||
|
||||
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
|
||||
// RangePicker place is an array
|
||||
if (isCreatePlaceholder && component !== 'DateRange' && component !== 'TimeRange' && component) {
|
||||
propsData.placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
|
||||
}
|
||||
propsData.codeField = field;
|
||||
propsData.formValues = unref(getValues);
|
||||
const getComponentValue = value => {
|
||||
if (numberItemType.includes(component)) {
|
||||
return value ?? 0;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const bindValue: Recordable = {
|
||||
[valueField || 'value']: getComponentValue(props.formModel[field]),
|
||||
};
|
||||
// 列表搜索时部分输入框按回车触发搜索事件
|
||||
const onPressEnter = () => {
|
||||
props.formActionType?.submit();
|
||||
};
|
||||
const debouncePressEnter = useDebounceFn(onPressEnter, 200);
|
||||
const keyupObj = component === 'Input' && unref(getComponentsProps)?.submitOnPressEnter ? { onPressEnter: debouncePressEnter } : {};
|
||||
|
||||
const compAttr: Recordable = {
|
||||
...propsData,
|
||||
...on,
|
||||
...bindValue,
|
||||
...keyupObj,
|
||||
};
|
||||
// 关闭输入框联想
|
||||
if (component === 'Input') compAttr.autoComplete = 'off';
|
||||
|
||||
if (!renderComponentContent) {
|
||||
return <Comp {...compAttr} />;
|
||||
}
|
||||
const compSlot = isFunction(renderComponentContent)
|
||||
? { ...renderComponentContent(unref(getValues)) }
|
||||
: {
|
||||
default: () => renderComponentContent,
|
||||
};
|
||||
return <Comp {...compAttr}>{compSlot}</Comp>;
|
||||
}
|
||||
|
||||
function renderLabelHelpMessage() {
|
||||
const { label, helpMessage, helpComponentProps, subLabel, component } = props.schema;
|
||||
if (noFieldComponents.includes(component) && !['Qrcode', 'Barcode'].includes(component)) {
|
||||
return '';
|
||||
}
|
||||
const renderLabel = subLabel ? (
|
||||
<span>
|
||||
{label} <span class="text-secondary">{subLabel}</span>
|
||||
</span>
|
||||
) : (
|
||||
label
|
||||
);
|
||||
const getHelpMessage = isFunction(helpMessage) ? helpMessage(unref(getValues)) : helpMessage;
|
||||
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
|
||||
return renderLabel;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
{renderLabel}
|
||||
<BasicHelp placement="top" text={getHelpMessage} {...helpComponentProps} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field, suffix, component, extra, className } = props.schema;
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
|
||||
const { colon } = props.formProps;
|
||||
|
||||
if (component === 'Divider') {
|
||||
return (
|
||||
<Col span={24}>
|
||||
<YunzhupaasDivider {...unref(getComponentsProps)} />
|
||||
</Col>
|
||||
);
|
||||
} else {
|
||||
const getContent = () => {
|
||||
return slot ? getSlot(slots, slot, unref(getValues)) : render ? render(unref(getValues)) : renderComponent();
|
||||
};
|
||||
|
||||
const showSuffix = !!suffix;
|
||||
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
|
||||
const name = noFieldComponents.includes(component) ? '' : field;
|
||||
const itemClassName = isArray(className) ? [...className] : [className];
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
name={name}
|
||||
colon={colon}
|
||||
class={[...itemClassName, { 'suffix-item': showSuffix }]}
|
||||
{...(itemProps as Recordable)}
|
||||
label={renderLabelHelpMessage()}
|
||||
extra={extra}
|
||||
rules={handleRules()}
|
||||
labelCol={labelCol}
|
||||
wrapperCol={wrapperCol}>
|
||||
<div style="display:flex">
|
||||
<div style="flex:1;">{getContent()}</div>
|
||||
{showSuffix && <span class="suffix">{getSuffix}</span>}
|
||||
</div>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { colProps = {}, colSlot, renderColContent, component } = props.schema;
|
||||
if (!componentMap.has(component)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { baseColProps = {} } = props.formProps;
|
||||
const realColProps = { ...baseColProps, ...colProps };
|
||||
const { isIfShow, isShow } = getShow();
|
||||
const values = unref(getValues);
|
||||
|
||||
const getContent = () => {
|
||||
return colSlot ? getSlot(slots, colSlot, values) : renderColContent ? renderColContent(values) : renderItem();
|
||||
};
|
||||
|
||||
return (
|
||||
isIfShow && (
|
||||
<Col {...realColProps} v-show={isShow}>
|
||||
{getContent()}
|
||||
</Col>
|
||||
)
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
73
src/components/Form/src/helper.ts
Normal file
73
src/components/Form/src/helper.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { Rule } from 'ant-design-vue/es/Form';
|
||||
import type { ComponentType } from './types/index';
|
||||
import { useI18n } from '@/hooks/web/useI18n';
|
||||
import { dateUtil, FormatDate } from '@/utils/dateUtil';
|
||||
import { isNumber, isObject } from '@/utils/is';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component.includes('Input') || component.includes('Complete')) {
|
||||
return t('common.inputText');
|
||||
}
|
||||
if (component.includes('Picker')) {
|
||||
return t('common.chooseText');
|
||||
}
|
||||
if (
|
||||
component.includes('Select') ||
|
||||
component.includes('Cascader') ||
|
||||
component.includes('Checkbox') ||
|
||||
component.includes('Radio') ||
|
||||
component.includes('Switch')
|
||||
) {
|
||||
// return `请选择${label}`;
|
||||
return t('common.chooseText');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker'];
|
||||
|
||||
function genType() {
|
||||
return [...DATE_TYPE, 'DateRange'];
|
||||
}
|
||||
|
||||
export function setComponentRuleType(rule: Rule, component: ComponentType, valueFormat: string) {
|
||||
if (['MonthPicker', 'WeekPicker'].includes(component)) {
|
||||
rule.type = valueFormat ? 'string' : 'object';
|
||||
} else if (['DateRange', 'TimeRange', 'Upload', 'CheckboxGroup'].includes(component)) {
|
||||
rule.type = 'array';
|
||||
} else if (['InputNumber', 'Switch', 'DatePicker'].includes(component)) {
|
||||
rule.type = 'number';
|
||||
}
|
||||
}
|
||||
|
||||
export function processDateValue(attr: Recordable, component: string) {
|
||||
const { valueFormat, value } = attr;
|
||||
if (valueFormat) {
|
||||
attr.value = isObject(value) ? dateUtil(value as FormatDate).format(valueFormat) : value;
|
||||
} else if (DATE_TYPE.includes(component) && value) {
|
||||
attr.value = dateUtil(attr.value);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleInputNumberValue(component?: ComponentType, val?: any) {
|
||||
if (!component) return val;
|
||||
if (['Input', 'InputPassword', 'InputSearch', 'TextArea'].includes(component)) {
|
||||
return val && isNumber(val) ? `${val}` : val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
export const dateItemType = genType();
|
||||
|
||||
export const defaultValueComponents = ['Input', 'InputPassword', 'InputSearch', 'TextArea'];
|
||||
export const noFieldComponents = ['Button', 'Divider', 'GroupTitle', 'Link', 'Text', 'Alert', 'Qrcode', 'Barcode'];
|
||||
export const numberItemType = ['Slider', 'Switch'];
|
||||
export const useInputComponents = ['BillRule', 'ModifyUser', 'ModifyTime'];
|
||||
158
src/components/Form/src/hooks/useAdvanced.ts
Normal file
158
src/components/Form/src/hooks/useAdvanced.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import type { ColEx } from '../types';
|
||||
import type { AdvanceState } from '../types/hooks';
|
||||
import { ComputedRef, getCurrentInstance, Ref, shallowReactive } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { computed, unref, watch } from 'vue';
|
||||
import { isBoolean, isFunction, isNumber, isObject } from '@/utils/is';
|
||||
import { useBreakpoint } from '@/hooks/event/useBreakpoint';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
const BASIC_COL_LEN = 24;
|
||||
|
||||
interface UseAdvancedContext {
|
||||
advanceState: AdvanceState;
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
}
|
||||
|
||||
export default function ({ advanceState, emit, getProps, getSchema, formModel, defaultValueRef }: UseAdvancedContext) {
|
||||
const vm = getCurrentInstance();
|
||||
|
||||
const fieldsIsAdvancedMap = shallowReactive({});
|
||||
|
||||
const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
|
||||
|
||||
const getEmptySpan = computed((): number => {
|
||||
if (!advanceState.isAdvanced) {
|
||||
return 0;
|
||||
}
|
||||
// For some special cases, you need to manually specify additional blank lines
|
||||
const emptySpan = unref(getProps).emptySpan || 0;
|
||||
|
||||
if (isNumber(emptySpan)) {
|
||||
return emptySpan;
|
||||
}
|
||||
if (isObject(emptySpan)) {
|
||||
const { span = 0 } = emptySpan;
|
||||
const screen = unref(screenRef) as string;
|
||||
|
||||
const screenSpan = (emptySpan as any)[screen.toLowerCase()];
|
||||
return screenSpan || span || 0;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 0);
|
||||
|
||||
watch(
|
||||
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
|
||||
() => {
|
||||
const { showAdvancedButton } = unref(getProps);
|
||||
if (showAdvancedButton) {
|
||||
debounceUpdateAdvanced();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function getAdvanced(itemCol: Partial<ColEx>, itemColSum = 0, isLastAction = false) {
|
||||
const width = unref(realWidthRef);
|
||||
|
||||
const mdWidth =
|
||||
parseInt(itemCol.md as string) || parseInt(itemCol.xs as string) || parseInt(itemCol.sm as string) || (itemCol.span as number) || BASIC_COL_LEN;
|
||||
|
||||
const lgWidth = parseInt(itemCol.lg as string) || mdWidth;
|
||||
const xlWidth = parseInt(itemCol.xl as string) || lgWidth;
|
||||
const xxlWidth = parseInt(itemCol.xxl as string) || xlWidth;
|
||||
if (width <= screenEnum.LG) {
|
||||
itemColSum += mdWidth;
|
||||
} else if (width < screenEnum.XL) {
|
||||
itemColSum += lgWidth;
|
||||
} else if (width < screenEnum.XXL) {
|
||||
itemColSum += xlWidth;
|
||||
} else {
|
||||
itemColSum += xxlWidth;
|
||||
}
|
||||
|
||||
if (isLastAction) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
if (itemColSum <= BASIC_COL_LEN * 2 - 6) {
|
||||
// When less than or equal to 2 lines, the collapse and expand buttons are not displayed
|
||||
advanceState.hideAdvanceBtn = true;
|
||||
// 表单组件默认展开问题修复
|
||||
// advanceState.isAdvanced = true;
|
||||
} else if (itemColSum > BASIC_COL_LEN * 2 - 6 && itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 30)) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
|
||||
// More than 3 lines collapsed by default
|
||||
} else if (!advanceState.isLoad) {
|
||||
advanceState.isLoad = true;
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
}
|
||||
if (itemColSum > BASIC_COL_LEN * (unref(getProps).alwaysShowLines || 1) - 6) {
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
} else {
|
||||
// The first line is always displayed
|
||||
return { isAdvanced: true, itemColSum };
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdvanced() {
|
||||
let itemColSum = 0;
|
||||
let realItemColSum = 0;
|
||||
const { baseColProps = {} } = unref(getProps);
|
||||
|
||||
for (const schema of unref(getSchema)) {
|
||||
const { show, colProps } = schema;
|
||||
let isShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
|
||||
if (isFunction(show)) {
|
||||
isShow = show({
|
||||
schema: schema,
|
||||
model: formModel,
|
||||
field: schema.field,
|
||||
values: {
|
||||
...unref(defaultValueRef),
|
||||
...formModel,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isShow && (colProps || baseColProps)) {
|
||||
const { itemColSum: sum, isAdvanced } = getAdvanced({ ...baseColProps, ...colProps }, itemColSum);
|
||||
|
||||
itemColSum = sum || 0;
|
||||
if (isAdvanced) {
|
||||
realItemColSum = itemColSum;
|
||||
}
|
||||
fieldsIsAdvancedMap[schema.field] = isAdvanced;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保页面发送更新(第一次不需要更新、第一次更新会报错)
|
||||
try {
|
||||
vm?.proxy?.$forceUpdate();
|
||||
} catch (error) {}
|
||||
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
|
||||
|
||||
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);
|
||||
|
||||
emit('advanced-change');
|
||||
}
|
||||
|
||||
function handleToggleAdvanced() {
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
|
||||
return { handleToggleAdvanced, fieldsIsAdvancedMap };
|
||||
}
|
||||
40
src/components/Form/src/hooks/useAutoFocus.ts
Normal file
40
src/components/Form/src/hooks/useAutoFocus.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormSchema, FormActionType, FormProps } from '../types/form';
|
||||
|
||||
import { unref, nextTick, watchEffect } from 'vue';
|
||||
|
||||
interface UseAutoFocusContext {
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
isInitedDefault: Ref<boolean>;
|
||||
formElRef: Ref<FormActionType>;
|
||||
}
|
||||
export async function useAutoFocus({
|
||||
getSchema,
|
||||
getProps,
|
||||
formElRef,
|
||||
isInitedDefault,
|
||||
}: UseAutoFocusContext) {
|
||||
watchEffect(async () => {
|
||||
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const schemas = unref(getSchema);
|
||||
const formEl = unref(formElRef);
|
||||
const el = (formEl as any)?.$el as HTMLElement;
|
||||
if (!formEl || !el || !schemas || schemas.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstItem = schemas[0];
|
||||
// Only open when the first form item is input type
|
||||
if (!firstItem.component.includes('Input')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
|
||||
if (!inputEl) return;
|
||||
inputEl?.focus();
|
||||
});
|
||||
}
|
||||
11
src/components/Form/src/hooks/useComponentRegister.ts
Normal file
11
src/components/Form/src/hooks/useComponentRegister.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { ComponentType } from '../types/index';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { add, del } from '../componentMap';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export function useComponentRegister(compName: ComponentType, comp: Component) {
|
||||
add(compName, comp);
|
||||
tryOnUnmounted(() => {
|
||||
del(compName);
|
||||
});
|
||||
}
|
||||
116
src/components/Form/src/hooks/useForm.ts
Normal file
116
src/components/Form/src/hooks/useForm.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import type { DynamicProps } from '#/utils';
|
||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
||||
import { isProdMode } from '@/utils/env';
|
||||
import { error } from '@/utils/log';
|
||||
import { getDynamicProps } from '@/utils';
|
||||
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
|
||||
|
||||
type Props = Partial<DynamicProps<FormProps>>;
|
||||
|
||||
export function useForm(props?: Props): UseFormReturnType {
|
||||
const formRef = ref<Nullable<FormActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
|
||||
async function getForm() {
|
||||
const form = unref(formRef);
|
||||
if (!form) {
|
||||
error('The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!');
|
||||
}
|
||||
await nextTick();
|
||||
return form as FormActionType;
|
||||
}
|
||||
|
||||
function register(instance: FormActionType) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
formRef.value = null;
|
||||
loadedRef.value = null;
|
||||
});
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
|
||||
|
||||
formRef.value = instance;
|
||||
loadedRef.value = true;
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
() => {
|
||||
props && instance.setProps(getDynamicProps(props));
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
|
||||
const form = await getForm();
|
||||
form.scrollToField(name, options);
|
||||
},
|
||||
setProps: async (formProps: Partial<FormProps>) => {
|
||||
const form = await getForm();
|
||||
form.setProps(formProps);
|
||||
},
|
||||
|
||||
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm();
|
||||
form.updateSchema(data);
|
||||
},
|
||||
|
||||
resetSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm();
|
||||
form.resetSchema(data);
|
||||
},
|
||||
|
||||
clearValidate: async (name?: string | string[]) => {
|
||||
const form = await getForm();
|
||||
form.clearValidate(name);
|
||||
},
|
||||
|
||||
resetFields: async () => {
|
||||
getForm().then(async form => {
|
||||
await form.resetFields();
|
||||
});
|
||||
},
|
||||
|
||||
removeSchemaByField: async (field: string | string[]) => {
|
||||
unref(formRef)?.removeSchemaByField(field);
|
||||
},
|
||||
|
||||
// TODO promisify
|
||||
getFieldsValue: <T>() => {
|
||||
return unref(formRef)?.getFieldsValue() as T;
|
||||
},
|
||||
|
||||
setFieldsValue: async <T extends Recordable<any>>(values: T) => {
|
||||
const form = await getForm();
|
||||
form.setFieldsValue<T>(values);
|
||||
},
|
||||
|
||||
appendSchemaByField: async (schema: FormSchema | FormSchema[], prefixField: string | undefined, first: boolean) => {
|
||||
const form = await getForm();
|
||||
form.appendSchemaByField(schema, prefixField, first);
|
||||
},
|
||||
|
||||
submit: async (): Promise<any> => {
|
||||
const form = await getForm();
|
||||
return form.submit();
|
||||
},
|
||||
|
||||
validate: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm();
|
||||
return form.validate(nameList);
|
||||
},
|
||||
|
||||
validateFields: async (nameList?: NamePath[]): Promise<Recordable> => {
|
||||
const form = await getForm();
|
||||
return form.validateFields(nameList);
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
17
src/components/Form/src/hooks/useFormContext.ts
Normal file
17
src/components/Form/src/hooks/useFormContext.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { InjectionKey } from 'vue';
|
||||
import { createContext, useContext } from '@/hooks/core/useContext';
|
||||
|
||||
export interface FormContextProps {
|
||||
resetAction: () => Promise<void>;
|
||||
submitAction: () => Promise<void>;
|
||||
}
|
||||
|
||||
const key: InjectionKey<FormContextProps> = Symbol();
|
||||
|
||||
export function createFormContext(context: FormContextProps) {
|
||||
return createContext<FormContextProps>(context, key);
|
||||
}
|
||||
|
||||
export function useFormContext() {
|
||||
return useContext<FormContextProps>(key);
|
||||
}
|
||||
318
src/components/Form/src/hooks/useFormEvents.ts
Normal file
318
src/components/Form/src/hooks/useFormEvents.ts
Normal file
@@ -0,0 +1,318 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormProps, FormSchema, FormActionType } from '../types/form';
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import { unref, toRaw, nextTick } from 'vue';
|
||||
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef, isEmpty } from '@/utils/is';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { dateItemType, handleInputNumberValue, defaultValueComponents, noFieldComponents } from '../helper';
|
||||
import { cloneDeep, uniqBy } from 'lodash-es';
|
||||
import { error } from '@/utils/log';
|
||||
|
||||
interface UseFormActionContext {
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
fullValueRef: Ref<Recordable>;
|
||||
formElRef: Ref<FormActionType>;
|
||||
schemaRef: Ref<FormSchema[]>;
|
||||
handleFormValues: Fn;
|
||||
isInitedDefaultRef: Ref<boolean>;
|
||||
}
|
||||
export function useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
defaultValueRef,
|
||||
fullValueRef,
|
||||
formElRef,
|
||||
schemaRef,
|
||||
handleFormValues,
|
||||
isInitedDefaultRef,
|
||||
}: UseFormActionContext) {
|
||||
async function resetFields(): Promise<void> {
|
||||
fullValueRef.value = {};
|
||||
const { resetFunc, submitOnReset } = unref(getProps);
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc());
|
||||
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
|
||||
Object.keys(formModel).forEach(key => {
|
||||
const schema = unref(getSchema).find(item => item.field === key);
|
||||
const isInput = schema?.component && defaultValueComponents.includes(schema.component);
|
||||
const defaultValue = cloneDeep(defaultValueRef.value[key]);
|
||||
formModel[key] = isInput ? defaultValue || '' : defaultValue;
|
||||
});
|
||||
nextTick(() => clearValidate());
|
||||
|
||||
emit('reset', toRaw(formModel));
|
||||
submitOnReset && handleSubmit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Set form value
|
||||
*/
|
||||
async function setFieldsValue(values: Recordable): Promise<void> {
|
||||
fullValueRef.value = { ...fullValueRef.value, ...values };
|
||||
const fields = unref(getSchema)
|
||||
.map(item => item.field)
|
||||
.filter(Boolean);
|
||||
|
||||
// key 支持 a.b.c 的嵌套写法
|
||||
const delimiter = '.';
|
||||
const nestKeyArray = fields.filter(item => item.indexOf(delimiter) >= 0);
|
||||
|
||||
const validKeys: string[] = [];
|
||||
Object.keys(values).forEach(key => {
|
||||
const schema = unref(getSchema).find(item => item.field === key);
|
||||
let value = values[key];
|
||||
|
||||
const hasKey = Reflect.has(values, key);
|
||||
|
||||
value = handleInputNumberValue(schema?.component, value);
|
||||
// 0| '' is allow
|
||||
if (hasKey && fields.includes(key)) {
|
||||
formModel[key] = value;
|
||||
validKeys.push(key);
|
||||
} else {
|
||||
nestKeyArray.forEach((nestKey: string) => {
|
||||
try {
|
||||
const value = nestKey.split('.').reduce((out, item) => out[item], values);
|
||||
if (isDef(value)) {
|
||||
formModel[nestKey] = value;
|
||||
validKeys.push(nestKey);
|
||||
}
|
||||
} catch (e) {
|
||||
// key not exist
|
||||
if (isDef(defaultValueRef.value[nestKey])) {
|
||||
formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
validateFields(validKeys).catch(_ => {});
|
||||
}
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
async function removeSchemaByField(fields: string | string[]): Promise<void> {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields;
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields];
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
_removeSchemaByField(field, schemaList);
|
||||
}
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
function _removeSchemaByField(field: string, schemaList: FormSchema[]): void {
|
||||
if (isString(field)) {
|
||||
const index = schemaList.findIndex(schema => schema.field === field);
|
||||
if (index !== -1) {
|
||||
delete formModel[field];
|
||||
schemaList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Insert after a certain field, if not insert the last
|
||||
*/
|
||||
async function appendSchemaByField(schema: FormSchema | FormSchema[], prefixField?: string, first = false) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
const addSchemaIds: string[] = Array.isArray(schema) ? schema.map(item => item.field) : [schema.field];
|
||||
if (schemaList.find(item => addSchemaIds.includes(item.field))) {
|
||||
error('There are schemas that have already been added');
|
||||
return;
|
||||
}
|
||||
const index = schemaList.findIndex(schema => schema.field === prefixField);
|
||||
const _schemaList = isObject(schema) ? [schema as FormSchema] : (schema as FormSchema[]);
|
||||
if (!prefixField || index === -1 || first) {
|
||||
first ? schemaList.unshift(..._schemaList) : schemaList.push(..._schemaList);
|
||||
schemaRef.value = schemaList;
|
||||
_setDefaultValue(schema);
|
||||
return;
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, ..._schemaList);
|
||||
}
|
||||
_setDefaultValue(schema);
|
||||
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
async function resetSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every(item => noFieldComponents.includes(item.component as string) || (Reflect.has(item, 'field') && item.field));
|
||||
|
||||
if (!hasField) {
|
||||
error('All children of the form Schema array that need to be updated must contain the `field` field');
|
||||
return;
|
||||
}
|
||||
schemaRef.value = updateData as FormSchema[];
|
||||
}
|
||||
|
||||
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every(item => noFieldComponents.includes(item.component as string) || (Reflect.has(item, 'field') && item.field));
|
||||
|
||||
if (!hasField) {
|
||||
error('All children of the form Schema array that need to be updated must contain the `field` field');
|
||||
return;
|
||||
}
|
||||
const schema: FormSchema[] = [];
|
||||
unref(getSchema).forEach(val => {
|
||||
let _val;
|
||||
updateData.forEach(item => {
|
||||
if (val.field === item.field) {
|
||||
_val = item;
|
||||
}
|
||||
});
|
||||
if (_val !== undefined && val.field === _val.field) {
|
||||
const newSchema = deepMerge(val, _val);
|
||||
if (Reflect.has(_val, 'componentProps') && Reflect.has(_val.componentProps, 'options')) {
|
||||
newSchema.componentProps.options = _val.componentProps.options;
|
||||
}
|
||||
schema.push(newSchema as FormSchema);
|
||||
} else {
|
||||
schema.push(val);
|
||||
}
|
||||
});
|
||||
_setDefaultValue(schema);
|
||||
|
||||
schemaRef.value = uniqBy(schema, 'field');
|
||||
isInitedDefaultRef.value = false;
|
||||
}
|
||||
|
||||
function _setDefaultValue(data: FormSchema | FormSchema[]) {
|
||||
let schemas: FormSchema[] = [];
|
||||
if (isObject(data)) {
|
||||
schemas.push(data as FormSchema);
|
||||
}
|
||||
if (isArray(data)) {
|
||||
schemas = [...data];
|
||||
}
|
||||
|
||||
const obj: Recordable = {};
|
||||
const currentFieldsValue = getFieldsValue();
|
||||
schemas.forEach(item => {
|
||||
if (
|
||||
!noFieldComponents.includes(item.component) &&
|
||||
Reflect.has(item, 'field') &&
|
||||
item.field &&
|
||||
!isNullOrUnDef(item.defaultValue) &&
|
||||
(!(item.field in currentFieldsValue) || isNullOrUnDef(currentFieldsValue[item.field]) || isEmpty(currentFieldsValue[item.field]))
|
||||
) {
|
||||
obj[item.field] = item.defaultValue;
|
||||
}
|
||||
});
|
||||
setFieldsValue(obj);
|
||||
}
|
||||
|
||||
function getFieldsValue(): Recordable {
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return {};
|
||||
return handleFormValues(toRaw(unref(formModel)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Is it time
|
||||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some(item => {
|
||||
return item.field === key ? dateItemType.includes(item.component) : false;
|
||||
});
|
||||
}
|
||||
|
||||
async function validateFields(nameList?: NamePath[] | undefined) {
|
||||
return unref(formElRef)?.validateFields(nameList);
|
||||
}
|
||||
|
||||
async function validate(nameList?: NamePath[] | undefined) {
|
||||
try {
|
||||
const values = await unref(formElRef)?.validate(nameList);
|
||||
return { ...fullValueRef.value, ...values };
|
||||
} catch (error: any) {
|
||||
if (!error.errorFields.length) {
|
||||
return { ...fullValueRef.value, ...error.values };
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function clearValidate(name?: string | string[]) {
|
||||
await unref(formElRef)?.clearValidate(name);
|
||||
}
|
||||
|
||||
async function scrollToField(name: NamePath, options?: ScrollOptions | undefined) {
|
||||
await unref(formElRef)?.scrollToField(name, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Form submission
|
||||
*/
|
||||
async function handleSubmit(e?: Event): Promise<void> {
|
||||
e && e.preventDefault();
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc();
|
||||
return;
|
||||
}
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
try {
|
||||
const values = await validate();
|
||||
const res = handleFormValues(values);
|
||||
emit('submit', res);
|
||||
} catch (error: any) {
|
||||
if (error?.outOfDate === false && error?.errorFields) {
|
||||
return;
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
clearValidate,
|
||||
validate,
|
||||
validateFields,
|
||||
getFieldsValue,
|
||||
updateSchema,
|
||||
resetSchema,
|
||||
appendSchemaByField,
|
||||
removeSchemaByField,
|
||||
resetFields,
|
||||
setFieldsValue,
|
||||
scrollToField,
|
||||
itemIsDateType,
|
||||
};
|
||||
}
|
||||
141
src/components/Form/src/hooks/useFormValues.ts
Normal file
141
src/components/Form/src/hooks/useFormValues.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '@/utils/is';
|
||||
import { dateUtil } from '@/utils/dateUtil';
|
||||
import { unref } from 'vue';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { cloneDeep, set } from 'lodash-es';
|
||||
|
||||
interface UseFormValuesContext {
|
||||
defaultValueRef: Ref<any>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
formModel: Recordable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription deconstruct array-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructArray(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\[(.+)\]$/;
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',');
|
||||
value = Array.isArray(value) ? value : [value];
|
||||
keys.forEach((k, index) => {
|
||||
set(target, k.trim(), value[index]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription deconstruct object-link key. This method will mutate the target.
|
||||
*/
|
||||
function tryDeconstructObject(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\{(.+)\}$/;
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',');
|
||||
value = isObject(value) ? value : {};
|
||||
keys.forEach(k => {
|
||||
set(target, k.trim(), value[k.trim()]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormValues({ defaultValueRef, getSchema, formModel, getProps }: UseFormValuesContext) {
|
||||
// Processing form values
|
||||
function handleFormValues(values: Recordable) {
|
||||
if (!isObject(values)) {
|
||||
return {};
|
||||
}
|
||||
const res: Recordable = {};
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item;
|
||||
const [key] = item;
|
||||
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
|
||||
continue;
|
||||
}
|
||||
const transformDateFunc = unref(getProps).transformDateFunc;
|
||||
if (isObject(value)) {
|
||||
value = transformDateFunc?.(value);
|
||||
}
|
||||
|
||||
if (isArray(value) && value[0]?.format && value[1]?.format) {
|
||||
value = value.map(item => transformDateFunc?.(item));
|
||||
}
|
||||
// Remove spaces
|
||||
if (isString(value)) {
|
||||
// remove params from URL
|
||||
if (value === '') {
|
||||
value = undefined;
|
||||
} else {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
|
||||
// 没有解构成功的,按原样赋值
|
||||
set(res, key, value);
|
||||
}
|
||||
}
|
||||
return handleRangeTimeValue(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Processing time interval parameters
|
||||
*/
|
||||
function handleRangeTimeValue(values: Recordable) {
|
||||
const fieldMapToTime = unref(getProps).fieldMapToTime;
|
||||
|
||||
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
for (const [field, [startTimeKey, endTimeKey], format = ''] of fieldMapToTime) {
|
||||
if (!field || !startTimeKey || !endTimeKey) {
|
||||
continue;
|
||||
}
|
||||
// If the value to be converted is empty, remove the field
|
||||
if (!values[field]) {
|
||||
Reflect.deleteProperty(values, field);
|
||||
continue;
|
||||
}
|
||||
|
||||
const [startTime, endTime]: string[] = values[field];
|
||||
if (format) {
|
||||
const [startTimeFormat, endTimeFormat] = Array.isArray(format) ? format : [format, format];
|
||||
values[startTimeKey] = dateUtil(startTime).format(startTimeFormat);
|
||||
values[endTimeKey] = dateUtil(endTime).format(endTimeFormat);
|
||||
} else {
|
||||
values[startTimeKey] = startTime;
|
||||
values[endTimeKey] = endTime;
|
||||
}
|
||||
|
||||
Reflect.deleteProperty(values, field);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function initDefault() {
|
||||
const schemas = unref(getSchema);
|
||||
const obj: Recordable = {};
|
||||
schemas.forEach(item => {
|
||||
const { defaultValue } = item;
|
||||
if (!isNullOrUnDef(defaultValue)) {
|
||||
obj[item.field] = defaultValue;
|
||||
|
||||
if (formModel[item.field] === undefined) {
|
||||
formModel[item.field] = defaultValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
defaultValueRef.value = cloneDeep(obj);
|
||||
}
|
||||
|
||||
return { handleFormValues, initDefault };
|
||||
}
|
||||
37
src/components/Form/src/hooks/useLabelWidth.ts
Normal file
37
src/components/Form/src/hooks/useLabelWidth.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, unref } from 'vue';
|
||||
import type { FormProps, FormSchema } from '../types/form';
|
||||
import { isNumber } from '@/utils/is';
|
||||
|
||||
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
||||
return computed(() => {
|
||||
const schemaItem = unref(schemaItemRef);
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem;
|
||||
|
||||
const { labelWidth: globalLabelWidth, labelCol: globalLabelCol, wrapperCol: globWrapperCol, layout } = unref(propsRef);
|
||||
|
||||
// If labelWidth is set globally, all items setting
|
||||
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
|
||||
labelCol.style = {
|
||||
textAlign: 'left',
|
||||
};
|
||||
return { labelCol, wrapperCol };
|
||||
}
|
||||
let width = labelWidth || globalLabelWidth;
|
||||
const col = { ...globalLabelCol, ...labelCol };
|
||||
const wrapCol = { ...globWrapperCol, ...wrapperCol };
|
||||
|
||||
if (width) {
|
||||
width = isNumber(width) ? `${width}px` : width;
|
||||
}
|
||||
|
||||
return {
|
||||
labelCol: { style: { width }, ...col },
|
||||
wrapperCol: {
|
||||
style: { width: layout === 'vertical' ? '100%' : `calc(100% - ${width})` },
|
||||
...wrapCol,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
104
src/components/Form/src/props.ts
Normal file
104
src/components/Form/src/props.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { FieldMapToTime, FormSchema } from './types/form';
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { ColEx } from './types';
|
||||
import type { TableActionType } from '@/components/Table';
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
|
||||
export const basicProps = {
|
||||
model: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => ({}),
|
||||
},
|
||||
// 标签宽度 固定宽度
|
||||
labelWidth: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 80,
|
||||
},
|
||||
fieldMapToTime: {
|
||||
type: Array as PropType<FieldMapToTime>,
|
||||
default: () => [],
|
||||
},
|
||||
compact: propTypes.bool,
|
||||
// 表单配置规则
|
||||
schemas: {
|
||||
type: Array as PropType<FormSchema[]>,
|
||||
default: () => [],
|
||||
},
|
||||
mergeDynamicData: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: null,
|
||||
},
|
||||
baseRowStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
baseColProps: {
|
||||
type: Object as PropType<Partial<ColEx>>,
|
||||
default: () => ({ span: 24 }),
|
||||
},
|
||||
autoSetPlaceHolder: propTypes.bool.def(true),
|
||||
// 在INPUT组件上单击回车时,是否自动提交
|
||||
autoSubmitOnEnter: propTypes.bool.def(false),
|
||||
submitOnReset: propTypes.bool,
|
||||
submitOnChange: propTypes.bool,
|
||||
size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
|
||||
// 禁用表单
|
||||
disabled: propTypes.bool,
|
||||
emptySpan: {
|
||||
type: [Number, Object] as PropType<number | Recordable>,
|
||||
default: 0,
|
||||
},
|
||||
// 是否显示收起展开按钮
|
||||
showAdvancedButton: propTypes.bool,
|
||||
// 转化时间
|
||||
transformDateFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: (date: any) => {
|
||||
return date?.format?.('YYYY-MM-DD HH:mm:ss') ?? date;
|
||||
},
|
||||
},
|
||||
rulesMessageJoinLabel: propTypes.bool.def(true),
|
||||
// 超过3行自动折叠
|
||||
autoAdvancedLine: propTypes.number.def(30),
|
||||
// 不受折叠影响的行数
|
||||
alwaysShowLines: propTypes.number.def(1),
|
||||
|
||||
// 是否显示操作按钮
|
||||
showActionButtonGroup: propTypes.bool.def(false),
|
||||
// 操作列Col配置
|
||||
actionColOptions: Object as PropType<Partial<ColEx>>,
|
||||
// 显示重置按钮
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
// 是否聚焦第一个输入框,只在第一个表单项为input的时候作用
|
||||
autoFocusFirstItem: propTypes.bool,
|
||||
// 重置按钮配置
|
||||
resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 显示确认按钮
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 自定义重置函数
|
||||
resetFunc: Function as PropType<() => Promise<void>>,
|
||||
submitFunc: Function as PropType<() => Promise<void>>,
|
||||
|
||||
// 以下为默认props
|
||||
hideRequiredMark: propTypes.bool,
|
||||
|
||||
labelCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
layout: propTypes.oneOf(['horizontal', 'vertical', 'inline']).def('horizontal'),
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
|
||||
wrapperCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
colon: propTypes.bool,
|
||||
|
||||
labelAlign: propTypes.string,
|
||||
|
||||
rowProps: Object as PropType<RowProps>,
|
||||
};
|
||||
209
src/components/Form/src/types/form.ts
Normal file
209
src/components/Form/src/types/form.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
|
||||
import type { VNode } from 'vue';
|
||||
import type { ButtonProps as AntdButtonProps } from '@/components/Button';
|
||||
import type { FormItem } from './formItem';
|
||||
import type { ColEx, ComponentType } from './index';
|
||||
import type { TableActionType } from '@/components/Table/src/types/table';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { RowProps } from 'ant-design-vue/lib/grid/Row';
|
||||
|
||||
export type FieldMapToTime = [string, [string, string], (string | [string, string])?][];
|
||||
|
||||
export type Rule = RuleObject & {
|
||||
trigger?: 'blur' | 'change' | ['change', 'blur'];
|
||||
};
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema;
|
||||
values: Recordable;
|
||||
model: Recordable;
|
||||
field: string;
|
||||
}
|
||||
|
||||
export interface ButtonProps extends AntdButtonProps {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<void>;
|
||||
setFieldsValue: <T extends Recordable<any>>(values: T) => Promise<void>;
|
||||
resetFields: () => Promise<void>;
|
||||
getFieldsValue: () => Recordable;
|
||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
resetSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
removeSchemaByField: (field: string | string[]) => Promise<void>;
|
||||
appendSchemaByField: (schema: FormSchema | FormSchema[], prefixField: string | undefined, first?: boolean | undefined) => Promise<void>;
|
||||
validateFields: (nameList?: NamePath[]) => Promise<any>;
|
||||
validate: (nameList?: NamePath[]) => Promise<any>;
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
|
||||
export interface FormProps {
|
||||
name?: string;
|
||||
layout?: 'vertical' | 'inline' | 'horizontal';
|
||||
// Form value
|
||||
model?: Recordable;
|
||||
// The width of all items in the entire form
|
||||
labelWidth?: number | string;
|
||||
// alignment
|
||||
labelAlign?: 'left' | 'right';
|
||||
// Row configuration for the entire form
|
||||
rowProps?: RowProps;
|
||||
// Submit form on reset
|
||||
submitOnReset?: boolean;
|
||||
// Submit form on form changing
|
||||
submitOnChange?: boolean;
|
||||
// Col configuration for the entire form
|
||||
labelCol?: Partial<ColEx>;
|
||||
// Col configuration for the entire form
|
||||
wrapperCol?: Partial<ColEx>;
|
||||
|
||||
// General row style
|
||||
baseRowStyle?: CSSProperties;
|
||||
|
||||
// General col configuration
|
||||
baseColProps?: Partial<ColEx>;
|
||||
|
||||
// Form configuration rules
|
||||
schemas?: FormSchema[];
|
||||
// Function values used to merge into dynamic control form items
|
||||
mergeDynamicData?: Recordable;
|
||||
// Compact mode for search forms
|
||||
compact?: boolean;
|
||||
// Blank line span
|
||||
emptySpan?: number | Partial<ColEx>;
|
||||
// Internal component size of the form
|
||||
size?: 'default' | 'small' | 'large';
|
||||
// Whether to disable
|
||||
disabled?: boolean;
|
||||
// Time interval fields are mapped into multiple
|
||||
fieldMapToTime?: FieldMapToTime;
|
||||
// Placeholder is set automatically
|
||||
autoSetPlaceHolder?: boolean;
|
||||
// Auto submit on press enter on input
|
||||
autoSubmitOnEnter?: boolean;
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
// Whether to show collapse and expand buttons
|
||||
showAdvancedButton?: boolean;
|
||||
// Whether to focus on the first input box, only works when the first form item is input
|
||||
autoFocusFirstItem?: boolean;
|
||||
// Automatically collapse over the specified number of rows
|
||||
autoAdvancedLine?: number;
|
||||
// Always show lines
|
||||
alwaysShowLines?: number;
|
||||
// Whether to show the operation button
|
||||
showActionButtonGroup?: boolean;
|
||||
|
||||
// Reset button configuration
|
||||
resetButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// Confirm button configuration
|
||||
submitButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// Operation column configuration
|
||||
actionColOptions?: Partial<ColEx>;
|
||||
|
||||
// Show reset button
|
||||
showResetButton?: boolean;
|
||||
// Show confirmation button
|
||||
showSubmitButton?: boolean;
|
||||
|
||||
resetFunc?: () => Promise<void>;
|
||||
submitFunc?: () => Promise<void>;
|
||||
transformDateFunc?: (date: any) => string;
|
||||
colon?: boolean;
|
||||
}
|
||||
export interface FormSchema {
|
||||
// Field name
|
||||
field: string;
|
||||
// Event name triggered by internal value change, default change
|
||||
changeEvent?: string;
|
||||
// Variable name bound to v-model Default value
|
||||
valueField?: string;
|
||||
className?: string | string[];
|
||||
// Label name
|
||||
label: string | VNode;
|
||||
extra?: string;
|
||||
// 权限编码控制是否显示
|
||||
auth?: string;
|
||||
// Auxiliary text
|
||||
subLabel?: string;
|
||||
// Help text on the right side of the text
|
||||
helpMessage?: string | string[] | ((renderCallbackParams: RenderCallbackParams) => string | string[]);
|
||||
// BaseHelp component props
|
||||
helpComponentProps?: Partial<HelpComponentProps>;
|
||||
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
|
||||
labelWidth?: string | number;
|
||||
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
|
||||
disabledLabelWidth?: boolean;
|
||||
// render component
|
||||
component: ComponentType;
|
||||
// Component parameters
|
||||
componentProps?: ((opt: { schema: FormSchema; tableAction: TableActionType; formActionType: FormActionType; formModel: Recordable }) => Recordable) | object;
|
||||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[];
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>;
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any;
|
||||
isAdvanced?: boolean;
|
||||
|
||||
// Matching details components
|
||||
span?: number;
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
// Render the content in the form-item tag
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
// Rendering col content requires outer wrapper form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
renderComponentContent?: ((renderCallbackParams: RenderCallbackParams) => any) | VNode | VNode[] | string;
|
||||
|
||||
// Custom slot, in from-item
|
||||
slot?: string;
|
||||
|
||||
// Custom slot, similar to renderColContent
|
||||
colSlot?: string;
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => Rule[];
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string;
|
||||
// Whether to display the serial number
|
||||
showIndex: boolean;
|
||||
// Text list
|
||||
text: any;
|
||||
// colour
|
||||
color: string;
|
||||
// font size
|
||||
fontSize: string;
|
||||
icon: string;
|
||||
absolute: boolean;
|
||||
// Positioning
|
||||
position: any;
|
||||
}
|
||||
91
src/components/Form/src/types/formItem.ts
Normal file
91
src/components/Form/src/types/formItem.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
import type { ColProps } from 'ant-design-vue/lib/grid/Col';
|
||||
import type { HTMLAttributes, VNodeChild } from 'vue';
|
||||
|
||||
export interface FormItem {
|
||||
/**
|
||||
* Used with label, whether to display : after label text.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
colon?: boolean;
|
||||
|
||||
/**
|
||||
* The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
extra?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Used with validateStatus, this option specifies the validation status icon. Recommended to be used only with Input.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
hasFeedback?: boolean;
|
||||
|
||||
/**
|
||||
* The prompt message. If not provided, the prompt message will be generated by the validation rule.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
help?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Label test
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
label?: string | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col>
|
||||
* @type Col
|
||||
*/
|
||||
labelCol?: ColProps & HTMLAttributes;
|
||||
|
||||
/**
|
||||
* Whether provided or not, it will be generated by the validation rule.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
required?: boolean;
|
||||
|
||||
/**
|
||||
* The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating'
|
||||
* @type string
|
||||
*/
|
||||
validateStatus?: '' | 'success' | 'warning' | 'error' | 'validating';
|
||||
|
||||
/**
|
||||
* The layout for input controls, same as labelCol
|
||||
* @type Col
|
||||
*/
|
||||
wrapperCol?: ColProps;
|
||||
/**
|
||||
* Set sub label htmlFor.
|
||||
*/
|
||||
htmlFor?: string;
|
||||
/**
|
||||
* text align of label
|
||||
*/
|
||||
labelAlign?: 'left' | 'right';
|
||||
/**
|
||||
* a key of model. In the setting of validate and resetFields method, the attribute is required
|
||||
*/
|
||||
name?: NamePath;
|
||||
/**
|
||||
* validation rules of form
|
||||
*/
|
||||
rules?: object | object[];
|
||||
/**
|
||||
* Whether to automatically associate form fields. In most cases, you can setting automatic association.
|
||||
* If the conditions for automatic association are not met, you can manually associate them. See the notes below.
|
||||
*/
|
||||
autoLink?: boolean;
|
||||
/**
|
||||
* Whether stop validate on first rule of error for this field.
|
||||
*/
|
||||
validateFirst?: boolean;
|
||||
/**
|
||||
* When to validate the value of children node
|
||||
*/
|
||||
validateTrigger?: string | string[] | false;
|
||||
}
|
||||
6
src/components/Form/src/types/hooks.ts
Normal file
6
src/components/Form/src/types/hooks.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface AdvanceState {
|
||||
isAdvanced: boolean;
|
||||
hideAdvanceBtn: boolean;
|
||||
isLoad: boolean;
|
||||
actionSpan: number;
|
||||
}
|
||||
150
src/components/Form/src/types/index.ts
Normal file
150
src/components/Form/src/types/index.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
type ColSpanType = number | string;
|
||||
export interface ColEx {
|
||||
style?: any;
|
||||
/**
|
||||
* raster number of cells to occupy, 0 corresponds to display: none
|
||||
* @default none (0)
|
||||
* @type ColSpanType
|
||||
*/
|
||||
span?: ColSpanType;
|
||||
|
||||
/**
|
||||
* raster order, used in flex layout mode
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
order?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the layout fill of flex
|
||||
* @default none
|
||||
* @type ColSpanType
|
||||
*/
|
||||
flex?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells to offset Col from the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
offset?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the right
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
push?: ColSpanType;
|
||||
|
||||
/**
|
||||
* the number of cells that raster is moved to the left
|
||||
* @default 0
|
||||
* @type ColSpanType
|
||||
*/
|
||||
pull?: ColSpanType;
|
||||
|
||||
/**
|
||||
* <576px and also default setting, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xs?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥576px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
sm?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥768px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
md?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥992px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
lg?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1200px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
|
||||
/**
|
||||
* ≥1600px, could be a span value or an object containing above props
|
||||
* @type { span: ColSpanType, offset: ColSpanType } | ColSpanType
|
||||
*/
|
||||
xxl?: { span: ColSpanType; offset: ColSpanType } | ColSpanType;
|
||||
}
|
||||
|
||||
export type ComponentType =
|
||||
| 'InputGroup'
|
||||
| 'InputSearch'
|
||||
| 'InputCountDown'
|
||||
| 'AutoComplete'
|
||||
| 'MonthPicker'
|
||||
| 'WeekPicker'
|
||||
| 'StrengthMeter'
|
||||
| 'IconPicker'
|
||||
| 'Render'
|
||||
| 'Alert'
|
||||
| 'AreaSelect'
|
||||
| 'Button'
|
||||
| 'Cron'
|
||||
| 'Cascader'
|
||||
| 'ColorPicker'
|
||||
| 'Checkbox'
|
||||
| 'YunzhupaasCheckboxSingle'
|
||||
| 'DatePicker'
|
||||
| 'DateRange'
|
||||
| 'TimePicker'
|
||||
| 'TimeRange'
|
||||
| 'Divider'
|
||||
| 'Editor'
|
||||
| 'GroupTitle'
|
||||
| 'Input'
|
||||
| 'InputPassword'
|
||||
| 'Textarea'
|
||||
| 'InputNumber'
|
||||
| 'Link'
|
||||
| 'OrganizeSelect'
|
||||
| 'DepSelect'
|
||||
| 'PosSelect'
|
||||
| 'GroupSelect'
|
||||
| 'RoleSelect'
|
||||
| 'UserSelect'
|
||||
| 'UsersSelect'
|
||||
| 'Qrcode'
|
||||
| 'Barcode'
|
||||
| 'Radio'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Slider'
|
||||
| 'Sign'
|
||||
| 'Signature'
|
||||
| 'Switch'
|
||||
| 'Text'
|
||||
| 'TreeSelect'
|
||||
| 'UploadFile'
|
||||
| 'UploadImg'
|
||||
| 'UploadImgSingle'
|
||||
| 'RelationForm'
|
||||
| 'RelationFormAttr'
|
||||
| 'PopupSelect'
|
||||
| 'PopupTableSelect'
|
||||
| 'PopupAttr'
|
||||
| 'NumberRange'
|
||||
| 'Calculate'
|
||||
| 'InputTable'
|
||||
| 'BillRule'
|
||||
| 'ModifyUser'
|
||||
| 'ModifyTime'
|
||||
| 'CreateUser'
|
||||
| 'CreateTime'
|
||||
| 'CurrOrganize'
|
||||
| 'CurrPosition'
|
||||
| 'Location'
|
||||
| 'Iframe';
|
||||
Reference in New Issue
Block a user