mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 14:13:40 +08:00
wip(form): perf form
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import BasicArrow from './src/BasicArrow.vue';
|
||||
|
||||
export const BasicArrow = createAsyncComponent(() => import('./src/BasicArrow.vue'));
|
||||
export { BasicArrow };
|
||||
|
||||
// export const BasicArrow = createAsyncComponent(() => import('./src/BasicArrow.vue'));
|
||||
export const BasicHelp = createAsyncComponent(() => import('./src/BasicHelp.vue'));
|
||||
export const BasicTitle = createAsyncComponent(() => import('./src/BasicTitle.vue'));
|
||||
|
@@ -101,7 +101,10 @@
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Form v-bind="{ ...$attrs, ...$props }" ref="formElRef" :model="formModel">
|
||||
<Row :class="getProps.compact ? 'compact-form-row' : ''" :style="getRowWrapStyleRef">
|
||||
<Row :class="getProps.compact ? 'compact-form-row' : ''" :style="getRowWrapStyle">
|
||||
<slot name="formHeader" />
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<FormItem
|
||||
@@ -10,6 +10,7 @@
|
||||
:formProps="getProps"
|
||||
:allDefaultValues="defaultValueRef"
|
||||
:formModel="formModel"
|
||||
:setFormModel="setFormModel"
|
||||
>
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data" />
|
||||
@@ -17,8 +18,9 @@
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<!-- -->
|
||||
<FormAction
|
||||
v-bind="{ ...getActionPropsRef, ...advanceState }"
|
||||
v-bind="{ ...getProps, ...advanceState }"
|
||||
@toggle-advanced="handleToggleAdvanced"
|
||||
/>
|
||||
<slot name="formFooter" />
|
||||
@@ -28,14 +30,12 @@
|
||||
<script lang="ts">
|
||||
import type { FormActionType, FormProps, FormSchema } from './types/form';
|
||||
import type { AdvanceState } from './types/hooks';
|
||||
import type { Ref, WatchStopHandle } from 'vue';
|
||||
import type { ValidateFields } from 'ant-design-vue/lib/form/interface';
|
||||
import type { CSSProperties, Ref, WatchStopHandle } from 'vue';
|
||||
|
||||
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, toRefs } from 'vue';
|
||||
import { Form, Row } from 'ant-design-vue';
|
||||
import FormItem from './FormItem';
|
||||
import { basicProps } from './props';
|
||||
import FormAction from './FormAction';
|
||||
import FormItem from './components/FormItem';
|
||||
import FormAction from './components/FormAction.vue';
|
||||
|
||||
import { dateItemType } from './helper';
|
||||
import moment from 'moment';
|
||||
@@ -44,7 +44,11 @@
|
||||
|
||||
import { useFormValues } from './hooks/useFormValues';
|
||||
import useAdvanced from './hooks/useAdvanced';
|
||||
import { useFormAction } from './hooks/useFormAction';
|
||||
import { useFormEvents } from './hooks/useFormEvents';
|
||||
import { createFormContext } from './hooks/useFormContext';
|
||||
|
||||
import { basicProps } from './props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicForm',
|
||||
components: { FormItem, Form, Row, FormAction },
|
||||
@@ -52,12 +56,7 @@
|
||||
props: basicProps,
|
||||
emits: ['advanced-change', 'reset', 'submit', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const formModel = reactive({});
|
||||
|
||||
const actionState = reactive({
|
||||
resetAction: {},
|
||||
submitAction: {},
|
||||
});
|
||||
const formModel = reactive<Recordable>({});
|
||||
|
||||
const advanceState = reactive<AdvanceState>({
|
||||
isAdvanced: true,
|
||||
@@ -66,37 +65,24 @@
|
||||
actionSpan: 6,
|
||||
});
|
||||
|
||||
const defaultValueRef = ref<any>({});
|
||||
const defaultValueRef = ref<Recordable>({});
|
||||
const isInitedDefaultRef = ref(false);
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||
const formElRef = ref<Nullable<FormActionType>>(null);
|
||||
|
||||
const getMergePropsRef = computed(
|
||||
// Get the basic configuration of the form
|
||||
const getProps = computed(
|
||||
(): FormProps => {
|
||||
return deepMerge(cloneDeep(props), unref(propsRef));
|
||||
}
|
||||
);
|
||||
|
||||
const getRowWrapStyleRef = computed((): any => {
|
||||
const { baseRowStyle } = unref(getMergePropsRef);
|
||||
return baseRowStyle || {};
|
||||
});
|
||||
|
||||
// 获取表单基本配置
|
||||
const getProps = computed(
|
||||
(): FormProps => {
|
||||
return {
|
||||
...unref(getMergePropsRef),
|
||||
resetButtonOptions: deepMerge(
|
||||
actionState.resetAction,
|
||||
unref(getMergePropsRef).resetButtonOptions || {}
|
||||
),
|
||||
submitButtonOptions: deepMerge(
|
||||
actionState.submitAction,
|
||||
unref(getMergePropsRef).submitButtonOptions || {}
|
||||
),
|
||||
};
|
||||
// Get uniform row style
|
||||
const getRowWrapStyle = computed(
|
||||
(): CSSProperties => {
|
||||
const { baseRowStyle = {} } = unref(getProps);
|
||||
return baseRowStyle;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -120,18 +106,19 @@
|
||||
return schemas as FormSchema[];
|
||||
});
|
||||
|
||||
const { getActionPropsRef, handleToggleAdvanced } = useAdvanced({
|
||||
const { handleToggleAdvanced } = useAdvanced({
|
||||
advanceState,
|
||||
emit,
|
||||
getMergePropsRef,
|
||||
getProps,
|
||||
getSchema,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
});
|
||||
|
||||
const { transformDateFunc, fieldMapToTime } = toRefs(props);
|
||||
|
||||
const { handleFormValues, initDefault } = useFormValues({
|
||||
transformDateFuncRef: transformDateFunc as Ref<Fn<any>>,
|
||||
transformDateFuncRef: transformDateFunc,
|
||||
fieldMapToTimeRef: fieldMapToTime,
|
||||
defaultValueRef,
|
||||
getSchema,
|
||||
@@ -139,7 +126,7 @@
|
||||
});
|
||||
|
||||
const {
|
||||
// handleSubmit,
|
||||
handleSubmit,
|
||||
setFieldsValue,
|
||||
clearValidate,
|
||||
validate,
|
||||
@@ -149,7 +136,8 @@
|
||||
appendSchemaByField,
|
||||
removeSchemaByFiled,
|
||||
resetFields,
|
||||
} = useFormAction({
|
||||
scrollToField,
|
||||
} = useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
@@ -158,14 +146,19 @@
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
schemaRef: schemaRef as Ref<FormSchema[]>,
|
||||
handleFormValues,
|
||||
actionState,
|
||||
});
|
||||
|
||||
createFormContext({
|
||||
resetAction: resetFields,
|
||||
submitAction: handleSubmit,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(getMergePropsRef).model,
|
||||
() => unref(getProps).model,
|
||||
() => {
|
||||
if (!unref(getMergePropsRef).model) return;
|
||||
setFieldsValue(unref(getMergePropsRef).model);
|
||||
const { model } = unref(getProps);
|
||||
if (!model) return;
|
||||
setFieldsValue(model);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@@ -178,16 +171,19 @@
|
||||
if (unref(isInitedDefaultRef)) {
|
||||
return stopWatch();
|
||||
}
|
||||
if (schema && schema.length) {
|
||||
if (schema?.length) {
|
||||
initDefault();
|
||||
isInitedDefaultRef.value = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function setProps(formProps: Partial<FormProps>): void {
|
||||
const mergeProps = deepMerge(unref(propsRef) || {}, formProps);
|
||||
propsRef.value = mergeProps;
|
||||
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||
}
|
||||
|
||||
function setFormModel(key: string, value: any) {
|
||||
formModel[key] = value;
|
||||
}
|
||||
|
||||
const formActionType: Partial<FormActionType> = {
|
||||
@@ -199,8 +195,10 @@
|
||||
removeSchemaByFiled,
|
||||
appendSchemaByField,
|
||||
clearValidate,
|
||||
validateFields: validateFields as ValidateFields,
|
||||
validate: validate as ValidateFields,
|
||||
validateFields,
|
||||
validate,
|
||||
submit: handleSubmit,
|
||||
scrollToField: scrollToField,
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -211,14 +209,14 @@
|
||||
return {
|
||||
handleToggleAdvanced,
|
||||
formModel,
|
||||
getActionPropsRef,
|
||||
defaultValueRef,
|
||||
advanceState,
|
||||
getRowWrapStyleRef,
|
||||
getRowWrapStyle,
|
||||
getProps,
|
||||
formElRef,
|
||||
getSchema,
|
||||
formActionType,
|
||||
setFormModel,
|
||||
...formActionType,
|
||||
};
|
||||
},
|
||||
|
@@ -1,141 +0,0 @@
|
||||
import type { ColEx } from './types/index';
|
||||
|
||||
import { defineComponent, unref, computed, PropType } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import { Button } from '/@/components/Button';
|
||||
import { BasicArrow } from '/@/components/Basic/index';
|
||||
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormAction',
|
||||
props: {
|
||||
show: propTypes.bool.def(true),
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
showAdvancedButton: propTypes.bool.def(true),
|
||||
resetButtonOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
submitButtonOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
actionColOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
actionSpan: propTypes.number.def(6),
|
||||
isAdvanced: propTypes.bool,
|
||||
hideAdvanceBtn: propTypes.bool,
|
||||
},
|
||||
emits: ['toggle-advanced'],
|
||||
setup(props, { slots, emit }) {
|
||||
const getResetBtnOptionsRef = computed(() => {
|
||||
return {
|
||||
text: t('component.form.resetButton'),
|
||||
...props.resetButtonOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const getSubmitBtnOptionsRef = computed(() => {
|
||||
return {
|
||||
text: t('component.form.submitButton'),
|
||||
// htmlType: 'submit',
|
||||
...props.submitButtonOptions,
|
||||
};
|
||||
});
|
||||
|
||||
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> = {
|
||||
span: showAdvancedButton ? 6 : 4,
|
||||
...advancedSpanObj,
|
||||
...actionColOptions,
|
||||
};
|
||||
return actionColOpt;
|
||||
});
|
||||
|
||||
function toggleAdvanced() {
|
||||
emit('toggle-advanced');
|
||||
}
|
||||
|
||||
function renderAdvanceButton() {
|
||||
const { showAdvancedButton, hideAdvanceBtn, isAdvanced } = props;
|
||||
|
||||
if (!showAdvancedButton || !!hideAdvanceBtn) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button type="default" class="mr-2" onClick={toggleAdvanced}>
|
||||
{() => (
|
||||
<>
|
||||
{isAdvanced ? t('component.form.putAway') : t('component.form.unfold')}
|
||||
<BasicArrow expand={!isAdvanced} top />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function renderResetButton() {
|
||||
const { showResetButton } = props;
|
||||
if (!showResetButton) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button type="default" class="mr-2" {...unref(getResetBtnOptionsRef)}>
|
||||
{() => unref(getResetBtnOptionsRef).text}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSubmitButton() {
|
||||
const { showSubmitButton } = props;
|
||||
if (!showSubmitButton) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button type="primary" {...unref(getSubmitBtnOptionsRef)}>
|
||||
{() => unref(getSubmitBtnOptionsRef).text}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Col {...unref(actionColOpt)} style={{ textAlign: 'right' }}>
|
||||
{() => (
|
||||
<Form.Item>
|
||||
{() => (
|
||||
<>
|
||||
{getSlot(slots, 'advanceBefore')}
|
||||
{renderAdvanceButton()}
|
||||
|
||||
{getSlot(slots, 'resetBefore')}
|
||||
{renderResetButton()}
|
||||
|
||||
{getSlot(slots, 'submitBefore')}
|
||||
{renderSubmitButton()}
|
||||
|
||||
{getSlot(slots, 'submitAfter')}
|
||||
</>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
import { Component } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import type { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
@@ -17,10 +17,11 @@ import {
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue';
|
||||
import { BasicUpload } from '/@/components/Upload';
|
||||
|
||||
const componentMap = new Map<ComponentType, any>();
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputGroup', Input.Group);
|
||||
|
139
src/components/Form/src/components/FormAction.vue
Normal file
139
src/components/Form/src/components/FormAction.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<a-col
|
||||
v-bind="actionColOpt"
|
||||
class="mb-2"
|
||||
:style="{ textAlign: 'right' }"
|
||||
v-if="showActionButtonGroup"
|
||||
>
|
||||
<FormItem>
|
||||
<slot name="resetBefore" />
|
||||
<Button
|
||||
type="default"
|
||||
class="mr-2"
|
||||
v-bind="getResetBtnOptions"
|
||||
@click="resetAction"
|
||||
v-if="showResetButton"
|
||||
>
|
||||
{{ getResetBtnOptions.text }}
|
||||
</Button>
|
||||
<slot name="submitBefore" />
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
class="mr-2"
|
||||
v-bind="getSubmitBtnOptions"
|
||||
@click="submitAction"
|
||||
v-if="showSubmitButton"
|
||||
>
|
||||
{{ getSubmitBtnOptions.text }}
|
||||
</Button>
|
||||
|
||||
<slot name="advanceBefore" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="toggleAdvanced"
|
||||
v-if="showAdvancedButton && !hideAdvanceBtn"
|
||||
>
|
||||
{{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
|
||||
<BasicArrow class="ml-1" :expand="!isAdvanced" top />
|
||||
</Button>
|
||||
<slot name="advanceAfter" />
|
||||
</FormItem>
|
||||
</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 } from 'ant-design-vue';
|
||||
import { Button } from '/@/components/Button';
|
||||
import { BasicArrow } from '/@/components/Basic/index';
|
||||
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,
|
||||
Button,
|
||||
BasicArrow,
|
||||
},
|
||||
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,
|
||||
},
|
||||
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> = {
|
||||
span: showAdvancedButton ? 6 : 4,
|
||||
...advancedSpanObj,
|
||||
...actionColOptions,
|
||||
};
|
||||
return actionColOpt;
|
||||
});
|
||||
|
||||
const getResetBtnOptions = computed(
|
||||
(): ButtonOptions => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('component.form.resetButton'),
|
||||
},
|
||||
props.resetButtonOptions
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const getSubmitBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
text: t('component.form.submitButton'),
|
||||
},
|
||||
props.submitButtonOptions
|
||||
);
|
||||
});
|
||||
|
||||
function toggleAdvanced() {
|
||||
emit('toggle-advanced');
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
actionColOpt,
|
||||
getResetBtnOptions,
|
||||
getSubmitBtnOptions,
|
||||
toggleAdvanced,
|
||||
...useFormContext(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -1,21 +1,21 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { FormActionType, FormProps } from './types/form';
|
||||
import type { FormSchema } from './types/form';
|
||||
import type { PropType, Ref } from 'vue';
|
||||
import type { FormActionType, FormProps } from '../types/form';
|
||||
import type { FormSchema } from '../types/form';
|
||||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
|
||||
import type { TableActionType } from '/@/components/Table';
|
||||
import type { ComponentType } from '../types';
|
||||
|
||||
import { defineComponent, computed, unref, toRef } from 'vue';
|
||||
import { defineComponent, computed, unref, toRefs } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import { componentMap } from './componentMap';
|
||||
import { componentMap } from '../componentMap';
|
||||
import { BasicHelp } from '/@/components/Basic';
|
||||
|
||||
import { isBoolean, isFunction } from '/@/utils/is';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { createPlaceholderMessage, setComponentRuleType } from '../helper';
|
||||
import { upperFirst, cloneDeep } from 'lodash-es';
|
||||
|
||||
import { useItemLabelWidth } from './hooks/useLabelWidth';
|
||||
import { ComponentType } from './types';
|
||||
import { useItemLabelWidth } from '../hooks/useLabelWidth';
|
||||
import { isNumber } from '/@/utils/is';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
@@ -32,13 +32,17 @@ export default defineComponent({
|
||||
default: {},
|
||||
},
|
||||
allDefaultValues: {
|
||||
type: Object as PropType<any>,
|
||||
type: Object as PropType<Recordable>,
|
||||
default: {},
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<any>,
|
||||
type: Object as PropType<Recordable>,
|
||||
default: {},
|
||||
},
|
||||
setFormModel: {
|
||||
type: Function as PropType<(key: string, value: any) => void>,
|
||||
default: null,
|
||||
},
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
@@ -48,10 +52,15 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const { t } = useI18n();
|
||||
// @ts-ignore
|
||||
const itemLabelWidthRef = useItemLabelWidth(toRef(props, 'schema'), toRef(props, 'formProps'));
|
||||
|
||||
const getValuesRef = computed(() => {
|
||||
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 {
|
||||
@@ -61,12 +70,12 @@ export default defineComponent({
|
||||
...mergeDynamicData,
|
||||
...allDefaultValues,
|
||||
...formModel,
|
||||
},
|
||||
} as Recordable,
|
||||
schema: schema,
|
||||
};
|
||||
});
|
||||
|
||||
const getComponentsPropsRef = computed(() => {
|
||||
const getComponentsProps = computed(() => {
|
||||
const { schema, tableAction, formModel, formActionType } = props;
|
||||
const { componentProps = {} } = schema;
|
||||
if (!isFunction(componentProps)) {
|
||||
@@ -75,19 +84,18 @@ export default defineComponent({
|
||||
return componentProps({ schema, tableAction, formModel, formActionType }) || {};
|
||||
});
|
||||
|
||||
const getDisableRef = computed(() => {
|
||||
const getDisable = computed(() => {
|
||||
const { disabled: globDisabled } = props.formProps;
|
||||
const { dynamicDisabled } = props.schema;
|
||||
const { disabled: itemDisabled = false } = unref(getComponentsPropsRef);
|
||||
const { disabled: itemDisabled = false } = unref(getComponentsProps);
|
||||
let disabled = !!globDisabled || itemDisabled;
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled;
|
||||
}
|
||||
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled(unref(getValuesRef));
|
||||
disabled = dynamicDisabled(unref(getValues));
|
||||
}
|
||||
|
||||
return disabled;
|
||||
});
|
||||
|
||||
@@ -109,10 +117,10 @@ export default defineComponent({
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show(unref(getValuesRef));
|
||||
isShow = show(unref(getValues));
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(unref(getValuesRef));
|
||||
isIfShow = ifShow(unref(getValues));
|
||||
}
|
||||
isShow = isShow && itemIsAdvanced;
|
||||
return { isShow, isIfShow };
|
||||
@@ -129,7 +137,7 @@ export default defineComponent({
|
||||
} = props.schema;
|
||||
|
||||
if (isFunction(dynamicRules)) {
|
||||
return dynamicRules(unref(getValuesRef)) as ValidationRule[];
|
||||
return dynamicRules(unref(getValues)) as ValidationRule[];
|
||||
}
|
||||
|
||||
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
|
||||
@@ -151,23 +159,15 @@ export default defineComponent({
|
||||
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
|
||||
? rulesMessageJoinLabel
|
||||
: globalRulesMessageJoinLabel;
|
||||
|
||||
rule.message =
|
||||
rule.message || createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
|
||||
|
||||
if (component.includes('Input') || component.includes('Textarea')) {
|
||||
rule.whitespace = true;
|
||||
}
|
||||
if (
|
||||
component.includes('DatePicker') ||
|
||||
component.includes('MonthPicker') ||
|
||||
component.includes('WeekPicker') ||
|
||||
component.includes('TimePicker')
|
||||
) {
|
||||
rule.type = 'object';
|
||||
} else if (component.includes('RangePicker') || component.includes('Upload')) {
|
||||
rule.type = 'array';
|
||||
} else if (component.includes('InputNumber')) {
|
||||
rule.type = 'number';
|
||||
}
|
||||
|
||||
setComponentRuleType(rule, component);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +181,12 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function handleValue(component: ComponentType, field: string) {
|
||||
const val = (props.formModel as any)[field];
|
||||
const val = props.formModel[field];
|
||||
if (['Input', 'InputPassword', 'InputSearch', 'InputTextArea'].includes(component)) {
|
||||
if (val && isNumber(val)) {
|
||||
(props.formModel as any)[field] = `${val}`;
|
||||
props.setFormModel(field, `${val}`);
|
||||
|
||||
// props.formModel[field] = `${val}`;
|
||||
return `${val}`;
|
||||
}
|
||||
return val;
|
||||
@@ -206,56 +208,59 @@ export default defineComponent({
|
||||
const eventKey = `on${upperFirst(changeEvent)}`;
|
||||
|
||||
const on = {
|
||||
[eventKey]: (e: any) => {
|
||||
[eventKey]: (e: Nullable<Recordable>) => {
|
||||
if (propsData[eventKey]) {
|
||||
propsData[eventKey](e);
|
||||
}
|
||||
|
||||
const target = e ? e.target : null;
|
||||
|
||||
const value = target ? (isCheck ? target.checked : target.value) : e;
|
||||
(props.formModel as any)[field] = value;
|
||||
props.setFormModel(field, value);
|
||||
// props.formModel[field] = value;
|
||||
},
|
||||
};
|
||||
const Comp = componentMap.get(component);
|
||||
const Comp = componentMap.get(component) as typeof defineComponent;
|
||||
|
||||
const { autoSetPlaceHolder, size } = props.formProps;
|
||||
const propsData: any = {
|
||||
const propsData: Recordable = {
|
||||
allowClear: true,
|
||||
getPopupContainer: (trigger: Element) => trigger.parentNode,
|
||||
size,
|
||||
...unref(getComponentsPropsRef),
|
||||
disabled: unref(getDisableRef),
|
||||
...unref(getComponentsProps),
|
||||
disabled: unref(getDisable),
|
||||
};
|
||||
|
||||
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
|
||||
let placeholder;
|
||||
// RangePicker place为数组
|
||||
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
|
||||
placeholder =
|
||||
(unref(getComponentsPropsRef) && unref(getComponentsPropsRef).placeholder) ||
|
||||
createPlaceholderMessage(component);
|
||||
placeholder = unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
|
||||
}
|
||||
propsData.placeholder = placeholder;
|
||||
propsData.codeField = field;
|
||||
propsData.formValues = unref(getValuesRef);
|
||||
const bindValue = {
|
||||
propsData.formValues = unref(getValues);
|
||||
|
||||
const bindValue: Recordable = {
|
||||
[valueField || (isCheck ? 'checked' : 'value')]: handleValue(component, field),
|
||||
};
|
||||
|
||||
const compAttr: Recordable = {
|
||||
...propsData,
|
||||
...on,
|
||||
...bindValue,
|
||||
};
|
||||
|
||||
if (!renderComponentContent) {
|
||||
return <Comp {...propsData} {...on} {...bindValue} />;
|
||||
return <Comp {...compAttr} />;
|
||||
}
|
||||
const compSlot = isFunction(renderComponentContent)
|
||||
? { ...renderComponentContent(unref(getValuesRef)) }
|
||||
? { ...renderComponentContent(unref(getValues)) }
|
||||
: {
|
||||
default: () => renderComponentContent,
|
||||
};
|
||||
|
||||
return (
|
||||
<Comp {...propsData} {...on} {...bindValue}>
|
||||
{compSlot}
|
||||
</Comp>
|
||||
);
|
||||
return <Comp {...compAttr}>{compSlot}</Comp>;
|
||||
}
|
||||
|
||||
function renderLabelHelpMessage() {
|
||||
@@ -280,20 +285,22 @@ export default defineComponent({
|
||||
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field } = props.schema;
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthRef);
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
|
||||
const { colon } = props.formProps;
|
||||
|
||||
const getContent = () => {
|
||||
return slot
|
||||
? getSlot(slots, slot, unref(getValuesRef))
|
||||
? getSlot(slots, slot, unref(getValues))
|
||||
: render
|
||||
? render(unref(getValuesRef))
|
||||
? render(unref(getValues))
|
||||
: renderComponent();
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
name={field}
|
||||
colon={colon}
|
||||
{...(itemProps as any)}
|
||||
{...(itemProps as Recordable)}
|
||||
label={renderLabelHelpMessage()}
|
||||
rules={handleRules()}
|
||||
labelCol={labelCol}
|
||||
@@ -306,20 +313,23 @@ export default defineComponent({
|
||||
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 getContent = () => {
|
||||
return colSlot
|
||||
? getSlot(slots, colSlot, unref(getValuesRef))
|
||||
? getSlot(slots, colSlot, unref(getValues))
|
||||
: renderColContent
|
||||
? renderColContent(unref(getValuesRef))
|
||||
? renderColContent(unref(getValues))
|
||||
: renderItem();
|
||||
};
|
||||
|
||||
return (
|
||||
isIfShow && (
|
||||
<Col {...realColProps} class={!isShow ? 'hidden' : ''}>
|
||||
<Col {...realColProps} class={{ hidden: !isShow }}>
|
||||
{() => getContent()}
|
||||
</Col>
|
||||
)
|
@@ -1,18 +1,23 @@
|
||||
<!--
|
||||
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
|
||||
-->
|
||||
|
||||
<template>
|
||||
<RadioGroup v-bind="$attrs" v-model:value="valueRef" button-style="solid">
|
||||
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
|
||||
<template v-for="item in getOptions" :key="`${item.value}`">
|
||||
<RadioButton :value="item.value"> {{ item.label }} </RadioButton>
|
||||
</template>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, PropType, watch, unref, computed } from 'vue';
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { Radio } from 'ant-design-vue';
|
||||
import {} from 'ant-design-vue/es/radio/Group';
|
||||
import { isString } from '/@/utils/is';
|
||||
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
type OptionsItem = { label: string; value: string; disabled?: boolean };
|
||||
type RadioItem = string | OptionsItem;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RadioButtonGroup',
|
||||
components: {
|
||||
@@ -28,34 +33,22 @@
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const valueRef = ref('');
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(v = '') => {
|
||||
valueRef.value = v;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => unref(valueRef),
|
||||
() => {
|
||||
emit('change', valueRef.value);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
setup(props) {
|
||||
const attrs = useAttrs();
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props);
|
||||
// Processing options value
|
||||
const getOptions = computed((): OptionsItem[] => {
|
||||
const { options } = props;
|
||||
if (!options || options.length === 0) return [];
|
||||
if (!options || options?.length === 0) return [];
|
||||
|
||||
const isStringArr = options.some((item) => isString(item));
|
||||
if (!isStringArr) return options as OptionsItem[];
|
||||
|
||||
return options.map((item) => ({ label: item, value: item })) as OptionsItem[];
|
||||
});
|
||||
|
||||
return { valueRef, getOptions };
|
||||
return { state, getOptions, attrs };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
|
||||
import type { ComponentType } from './types/index';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
@@ -30,6 +31,16 @@ function genType() {
|
||||
return ['DatePicker', 'MonthPicker', 'RangePicker', 'WeekPicker', 'TimePicker'];
|
||||
}
|
||||
|
||||
export function setComponentRuleType(rule: ValidationRule, component: ComponentType) {
|
||||
if (['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'].includes(component)) {
|
||||
rule.type = 'object';
|
||||
} else if (['RangePicker', 'Upload', 'CheckboxGroup', 'TimePicker'].includes(component)) {
|
||||
rule.type = 'array';
|
||||
} else if (['InputNumber'].includes(component)) {
|
||||
rule.type = 'number';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
|
@@ -13,28 +13,28 @@ const BASIC_COL_LEN = 24;
|
||||
interface UseAdvancedContext {
|
||||
advanceState: AdvanceState;
|
||||
emit: EmitType;
|
||||
getMergePropsRef: ComputedRef<FormProps>;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: any;
|
||||
defaultValueRef: Ref<any>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
}
|
||||
|
||||
export default function ({
|
||||
advanceState,
|
||||
emit,
|
||||
getMergePropsRef,
|
||||
getProps,
|
||||
getSchema,
|
||||
formModel,
|
||||
defaultValueRef,
|
||||
}: UseAdvancedContext) {
|
||||
const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
|
||||
const getEmptySpanRef = computed((): number => {
|
||||
|
||||
const getEmptySpan = computed((): number => {
|
||||
if (!advanceState.isAdvanced) {
|
||||
return 0;
|
||||
}
|
||||
const emptySpan = unref(getMergePropsRef).emptySpan || 0;
|
||||
// For some special cases, you need to manually specify additional blank lines
|
||||
const emptySpan = unref(getProps).emptySpan || 0;
|
||||
|
||||
if (isNumber(emptySpan)) {
|
||||
return emptySpan;
|
||||
@@ -49,27 +49,6 @@ export default function ({
|
||||
return 0;
|
||||
});
|
||||
|
||||
const getActionPropsRef = computed(() => {
|
||||
const {
|
||||
resetButtonOptions,
|
||||
submitButtonOptions,
|
||||
showActionButtonGroup,
|
||||
showResetButton,
|
||||
showSubmitButton,
|
||||
showAdvancedButton,
|
||||
actionColOptions,
|
||||
} = unref(getProps);
|
||||
return {
|
||||
resetButtonOptions,
|
||||
submitButtonOptions,
|
||||
show: showActionButtonGroup,
|
||||
showResetButton,
|
||||
showSubmitButton,
|
||||
showAdvancedButton,
|
||||
actionColOptions,
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
|
||||
() => {
|
||||
@@ -90,6 +69,7 @@ export default function ({
|
||||
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;
|
||||
@@ -102,15 +82,16 @@ export default function ({
|
||||
} else {
|
||||
itemColSum += xxlWidth;
|
||||
}
|
||||
|
||||
if (isLastAction) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
if (itemColSum <= BASIC_COL_LEN * 2) {
|
||||
// 小于等于2行时,不显示收起展开按钮
|
||||
// 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 &&
|
||||
itemColSum <= BASIC_COL_LEN * (unref(getMergePropsRef).autoAdvancedLine || 3)
|
||||
itemColSum <= BASIC_COL_LEN * (unref(getProps).autoAdvancedLine || 3)
|
||||
) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
|
||||
@@ -168,13 +149,9 @@ export default function ({
|
||||
}
|
||||
}
|
||||
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpanRef);
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
|
||||
|
||||
getAdvanced(
|
||||
unref(getActionPropsRef).actionColOptions || { span: BASIC_COL_LEN },
|
||||
itemColSum,
|
||||
true
|
||||
);
|
||||
getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);
|
||||
|
||||
emit('advanced-change');
|
||||
}
|
||||
@@ -182,5 +159,6 @@ export default function ({
|
||||
function handleToggleAdvanced() {
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
return { getActionPropsRef, handleToggleAdvanced };
|
||||
|
||||
return { handleToggleAdvanced };
|
||||
}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import type { ComponentType } from '../types/index';
|
||||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
||||
import { add, del } from '../componentMap';
|
||||
export function useComponentRegister(compName: ComponentType, comp: any) {
|
||||
import type { Component } from 'vue';
|
||||
|
||||
export function useComponentRegister(compName: ComponentType, comp: Component) {
|
||||
add(compName, comp);
|
||||
tryOnUnmounted(() => {
|
||||
del(compName);
|
||||
|
@@ -1,23 +1,28 @@
|
||||
import { ref, onUnmounted, unref } from 'vue';
|
||||
import { ref, onUnmounted, unref, nextTick } from 'vue';
|
||||
|
||||
import { isInSetup } from '/@/utils/helper/vueHelper';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { error } from '/@/utils/log';
|
||||
|
||||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
|
||||
import type { NamePath } from 'ant-design-vue/lib/form/interface';
|
||||
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<any>;
|
||||
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
|
||||
|
||||
export function useForm(props?: Partial<FormProps>): UseFormReturnType {
|
||||
isInSetup();
|
||||
const formRef = ref<FormActionType | null>(null);
|
||||
const loadedRef = ref<boolean | null>(false);
|
||||
|
||||
function getForm() {
|
||||
const formRef = ref<Nullable<FormActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
|
||||
async function getForm() {
|
||||
const form = unref(formRef);
|
||||
if (!form) {
|
||||
throw new Error('formRef is Null');
|
||||
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) {
|
||||
@@ -27,45 +32,73 @@ export function useForm(props?: Partial<FormProps>): UseFormReturnType {
|
||||
loadedRef.value = null;
|
||||
});
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
|
||||
|
||||
formRef.value = instance;
|
||||
props && instance.setProps(props);
|
||||
loadedRef.value = true;
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
setProps: (formProps: Partial<FormProps>) => {
|
||||
getForm().setProps(formProps);
|
||||
scrollToField: async (name: NamePath, options?: ScrollOptions | undefined) => {
|
||||
const form = await getForm();
|
||||
form.scrollToField(name, options);
|
||||
},
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
getForm().updateSchema(data);
|
||||
setProps: async (formProps: Partial<FormProps>) => {
|
||||
const form = await getForm();
|
||||
form.setProps(formProps);
|
||||
},
|
||||
clearValidate: (name?: string | string[]) => {
|
||||
getForm().clearValidate(name);
|
||||
|
||||
updateSchema: async (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
const form = await getForm();
|
||||
form.updateSchema(data);
|
||||
},
|
||||
|
||||
clearValidate: async (name?: string | string[]) => {
|
||||
const form = await getForm();
|
||||
form.clearValidate(name);
|
||||
},
|
||||
|
||||
resetFields: async () => {
|
||||
await getForm().resetFields();
|
||||
getForm().then(async (form) => {
|
||||
await form.resetFields();
|
||||
});
|
||||
},
|
||||
removeSchemaByFiled: (field: string | string[]) => {
|
||||
getForm().removeSchemaByFiled(field);
|
||||
|
||||
removeSchemaByFiled: async (field: string | string[]) => {
|
||||
const form = await getForm();
|
||||
form.removeSchemaByFiled(field);
|
||||
},
|
||||
getFieldsValue: () => {
|
||||
return getForm().getFieldsValue();
|
||||
|
||||
// TODO promisify
|
||||
getFieldsValue: <T>() => {
|
||||
return unref(formRef)?.getFieldsValue() as T;
|
||||
},
|
||||
setFieldsValue: <T>(values: T) => {
|
||||
getForm().setFieldsValue<T>(values);
|
||||
|
||||
setFieldsValue: async <T>(values: T) => {
|
||||
const form = await getForm();
|
||||
form.setFieldsValue<T>(values);
|
||||
},
|
||||
appendSchemaByField: (schema: FormSchema, prefixField?: string | undefined) => {
|
||||
getForm().appendSchemaByField(schema, prefixField);
|
||||
|
||||
appendSchemaByField: async (schema: FormSchema, prefixField?: string | undefined) => {
|
||||
const form = await getForm();
|
||||
form.appendSchemaByField(schema, prefixField);
|
||||
},
|
||||
|
||||
submit: async (): Promise<any> => {
|
||||
return getForm().submit();
|
||||
const form = await getForm();
|
||||
return form.submit();
|
||||
},
|
||||
validate: ((async (nameList?: NamePath[]): Promise<any> => {
|
||||
return getForm().validate(nameList);
|
||||
}) as any) as ValidateFields,
|
||||
validateFields: ((async (nameList?: NamePath[]): Promise<any> => {
|
||||
return getForm().validate(nameList);
|
||||
}) as any) as ValidateFields,
|
||||
} as FormActionType;
|
||||
|
||||
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 { 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);
|
||||
}
|
@@ -9,22 +9,19 @@ import { deepMerge, unique } from '/@/utils';
|
||||
import { dateItemType } from '../helper';
|
||||
import moment from 'moment';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { error } from '/@/utils/log';
|
||||
|
||||
interface UseFormActionContext {
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: any;
|
||||
defaultValueRef: Ref<any>;
|
||||
formModel: Recordable;
|
||||
defaultValueRef: Ref<Recordable>;
|
||||
formElRef: Ref<FormActionType>;
|
||||
schemaRef: Ref<FormSchema[]>;
|
||||
handleFormValues: Fn;
|
||||
actionState: {
|
||||
resetAction: any;
|
||||
submitAction: any;
|
||||
};
|
||||
}
|
||||
export function useFormAction({
|
||||
export function useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
@@ -33,34 +30,34 @@ export function useFormAction({
|
||||
formElRef,
|
||||
schemaRef,
|
||||
handleFormValues,
|
||||
actionState,
|
||||
}: UseFormActionContext) {
|
||||
async function resetFields(): Promise<any> {
|
||||
async function resetFields(): Promise<void> {
|
||||
const { resetFunc, submitOnReset } = unref(getProps);
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc());
|
||||
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
|
||||
Object.keys(formModel).forEach((key) => {
|
||||
(formModel as any)[key] = defaultValueRef.value[key];
|
||||
formModel[key] = defaultValueRef.value[key];
|
||||
});
|
||||
clearValidate();
|
||||
emit('reset', toRaw(formModel));
|
||||
// return values;
|
||||
submitOnReset && handleSubmit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 设置表单值
|
||||
* @description: Set form value
|
||||
*/
|
||||
async function setFieldsValue(values: any): Promise<void> {
|
||||
const fields = unref(getSchema)
|
||||
.map((item) => item.field)
|
||||
.filter(Boolean);
|
||||
// const formEl = unref(formElRef);
|
||||
|
||||
const validKeys: string[] = [];
|
||||
Object.keys(values).forEach((key) => {
|
||||
const element = values[key];
|
||||
// 0| '' is allow
|
||||
if (element !== undefined && element !== null && fields.includes(key)) {
|
||||
// time type
|
||||
if (itemIsDateType(key)) {
|
||||
@@ -69,12 +66,12 @@ export function useFormAction({
|
||||
for (const ele of element) {
|
||||
arr.push(moment(ele));
|
||||
}
|
||||
(formModel as any)[key] = arr;
|
||||
formModel[key] = arr;
|
||||
} else {
|
||||
(formModel as any)[key] = moment(element);
|
||||
formModel[key] = moment(element);
|
||||
}
|
||||
} else {
|
||||
(formModel as any)[key] = element;
|
||||
formModel[key] = element;
|
||||
}
|
||||
validKeys.push(key);
|
||||
}
|
||||
@@ -84,19 +81,18 @@ export function useFormAction({
|
||||
/**
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
function removeSchemaByFiled(fields: string | string[]): void {
|
||||
async function removeSchemaByFiled(fields: string | string[]): Promise<void> {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
let fieldList: string[] = fields as string[];
|
||||
if (!fields) return;
|
||||
|
||||
let fieldList: string[] = isString(fields) ? [fields] : fields;
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields];
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
_removeSchemaByFiled(field, schemaList);
|
||||
}
|
||||
schemaRef.value = schemaList as any;
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,27 +110,26 @@ export function useFormAction({
|
||||
/**
|
||||
* @description: Insert after a certain field, if not insert the last
|
||||
*/
|
||||
function appendSchemaByField(schema: FormSchema, prefixField?: string) {
|
||||
async function appendSchemaByField(schema: FormSchema, prefixField?: string, first = false) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField);
|
||||
const hasInList = schemaList.find((item) => item.field === schema.field);
|
||||
const hasInList = schemaList.some((item) => item.field === schema.field);
|
||||
|
||||
if (hasInList) {
|
||||
return;
|
||||
}
|
||||
if (!prefixField || index === -1) {
|
||||
schemaList.push(schema);
|
||||
schemaRef.value = schemaList as any;
|
||||
if (!hasInList) return;
|
||||
|
||||
if (!prefixField || index === -1 || first) {
|
||||
first ? schemaList.unshift(schema) : schemaList.push(schema);
|
||||
schemaRef.value = schemaList;
|
||||
return;
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, schema);
|
||||
}
|
||||
schemaRef.value = schemaList as any;
|
||||
schemaRef.value = schemaList;
|
||||
}
|
||||
|
||||
function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
async function updateSchema(data: Partial<FormSchema> | Partial<FormSchema>[]) {
|
||||
let updateData: Partial<FormSchema>[] = [];
|
||||
if (isObject(data)) {
|
||||
updateData.push(data as FormSchema);
|
||||
@@ -142,9 +137,13 @@ export function useFormAction({
|
||||
if (isArray(data)) {
|
||||
updateData = [...data];
|
||||
}
|
||||
|
||||
const hasField = updateData.every((item) => Reflect.has(item, 'field') && item.field);
|
||||
|
||||
if (!hasField) {
|
||||
throw new Error('Must pass in the `field` field!');
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field'
|
||||
);
|
||||
}
|
||||
const schema: FormSchema[] = [];
|
||||
updateData.forEach((item) => {
|
||||
@@ -157,12 +156,12 @@ export function useFormAction({
|
||||
}
|
||||
});
|
||||
});
|
||||
schemaRef.value = unique(schema, 'field') as any;
|
||||
schemaRef.value = unique(schema, 'field');
|
||||
}
|
||||
|
||||
function getFieldsValue(): any {
|
||||
function getFieldsValue(): Recordable {
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
if (!formEl) return {};
|
||||
return handleFormValues(toRaw(unref(formModel)));
|
||||
}
|
||||
|
||||
@@ -171,23 +170,24 @@ export function useFormAction({
|
||||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some((item) => {
|
||||
return item.field === key ? dateItemType.includes(item.component!) : false;
|
||||
return item.field === key ? dateItemType.includes(item.component) : false;
|
||||
});
|
||||
}
|
||||
|
||||
function validateFields(nameList?: NamePath[] | undefined) {
|
||||
if (!formElRef.value) return;
|
||||
return formElRef.value.validateFields(nameList);
|
||||
async function validateFields(nameList?: NamePath[] | undefined) {
|
||||
return await unref(formElRef)?.validateFields(nameList);
|
||||
}
|
||||
|
||||
function validate(nameList?: NamePath[] | undefined) {
|
||||
if (!formElRef.value) return;
|
||||
return formElRef.value.validate(nameList);
|
||||
async function validate(nameList?: NamePath[] | undefined) {
|
||||
return await unref(formElRef)?.validate(nameList);
|
||||
}
|
||||
|
||||
function clearValidate(name?: string | string[]) {
|
||||
if (!formElRef.value) return;
|
||||
formElRef.value.clearValidate(name);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,13 +208,6 @@ export function useFormAction({
|
||||
emit('submit', res);
|
||||
} catch (error) {}
|
||||
}
|
||||
actionState.resetAction = {
|
||||
onClick: resetFields,
|
||||
};
|
||||
|
||||
actionState.submitAction = {
|
||||
onClick: handleSubmit,
|
||||
};
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
@@ -227,5 +220,6 @@ export function useFormAction({
|
||||
removeSchemaByFiled,
|
||||
resetFields,
|
||||
setFieldsValue,
|
||||
scrollToField,
|
||||
};
|
||||
}
|
@@ -9,7 +9,7 @@ interface UseFormValuesContext {
|
||||
fieldMapToTimeRef: Ref<FieldMapToTime>;
|
||||
defaultValueRef: Ref<any>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: any;
|
||||
formModel: Recordable;
|
||||
}
|
||||
export function useFormValues({
|
||||
transformDateFuncRef,
|
||||
@@ -19,11 +19,11 @@ export function useFormValues({
|
||||
formModel,
|
||||
}: UseFormValuesContext) {
|
||||
// Processing form values
|
||||
function handleFormValues(values: Record<string, any>) {
|
||||
function handleFormValues(values: Recordable) {
|
||||
if (!isObject(values)) {
|
||||
return {};
|
||||
}
|
||||
const resMap: Record<string, any> = {};
|
||||
const res: Recordable = {};
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item;
|
||||
const [key] = item;
|
||||
@@ -41,15 +41,15 @@ export function useFormValues({
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
}
|
||||
resMap[key] = value;
|
||||
res[key] = value;
|
||||
}
|
||||
return handleRangeTimeValue(resMap);
|
||||
return handleRangeTimeValue(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Processing time interval parameters
|
||||
*/
|
||||
function handleRangeTimeValue(values: Record<string, any>) {
|
||||
function handleRangeTimeValue(values: Recordable) {
|
||||
const fieldMapToTime = unref(fieldMapToTimeRef);
|
||||
|
||||
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
|
||||
@@ -65,6 +65,7 @@ export function useFormValues({
|
||||
|
||||
values[startTimeKey] = moment(startTime).format(format);
|
||||
values[endTimeKey] = moment(endTime).format(format);
|
||||
Reflect.deleteProperty(values, field);
|
||||
}
|
||||
|
||||
return values;
|
||||
@@ -72,11 +73,11 @@ export function useFormValues({
|
||||
|
||||
function initDefault() {
|
||||
const schemas = unref(getSchema);
|
||||
const obj: Record<string, any> = {};
|
||||
const obj: Recordable = {};
|
||||
schemas.forEach((item) => {
|
||||
if (item.defaultValue) {
|
||||
obj[item.field] = item.defaultValue;
|
||||
(formModel as any)[item.field] = item.defaultValue;
|
||||
formModel[item.field] = item.defaultValue;
|
||||
}
|
||||
});
|
||||
defaultValueRef.value = obj;
|
||||
|
@@ -4,23 +4,8 @@ import type { FormProps, FormSchema } from '../types/form';
|
||||
import { computed, unref } from 'vue';
|
||||
import { isNumber } from '/@/utils/is';
|
||||
|
||||
// export function useGlobalLabelWidth(propsRef: ComputedRef<FormProps>) {
|
||||
// return computed(() => {
|
||||
// const { labelWidth, labelCol, wrapperCol } = unref(propsRef);
|
||||
// if (!labelWidth) {
|
||||
// return { labelCol, wrapperCol };
|
||||
// }
|
||||
|
||||
// const width = isNumber(labelWidth) ? `${labelWidth}px` : labelWidth;
|
||||
// return {
|
||||
// labelCol: { style: { width }, span: 1, ...labelCol },
|
||||
// wrapperCol: { style: { width: `calc(100% - ${width})` }, span: 23, ...wrapperCol },
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
|
||||
export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<FormProps>) {
|
||||
return computed((): any => {
|
||||
return computed(() => {
|
||||
const schemaItem = unref(schemaItemRef);
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem;
|
||||
@@ -29,7 +14,7 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
|
||||
labelWidth: globalLabelWidth,
|
||||
labelCol: globalLabelCol,
|
||||
wrapperCol: globWrapperCol,
|
||||
} = unref(propsRef) as any;
|
||||
} = unref(propsRef);
|
||||
|
||||
// If labelWidth is set globally, all items setting
|
||||
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import type { FieldMapToTime, FormSchema } from './types/form';
|
||||
import type { PropType } from 'vue';
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { ColEx } from './types';
|
||||
import { TableActionType } from '/@/components/Table';
|
||||
import type { TableActionType } from '/@/components/Table';
|
||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export const basicProps = {
|
||||
model: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
type: Object as PropType<Recordable>,
|
||||
default: {},
|
||||
},
|
||||
// 标签宽度 固定宽度
|
||||
@@ -17,7 +20,7 @@ export const basicProps = {
|
||||
type: Array as PropType<FieldMapToTime>,
|
||||
default: () => [],
|
||||
},
|
||||
compact: Boolean as PropType<boolean>,
|
||||
compact: propTypes.bool,
|
||||
// 表单配置规则
|
||||
schemas: {
|
||||
type: [Array] as PropType<FormSchema[]>,
|
||||
@@ -25,98 +28,68 @@ export const basicProps = {
|
||||
required: true,
|
||||
},
|
||||
mergeDynamicData: {
|
||||
type: Object as PropType<any>,
|
||||
type: Object as PropType<Recordable>,
|
||||
default: null,
|
||||
},
|
||||
baseRowStyle: {
|
||||
type: Object as PropType<any>,
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
baseColProps: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
autoSetPlaceHolder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
submitOnReset: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<'default' | 'small' | 'large'>,
|
||||
default: 'default',
|
||||
type: Object as PropType<Partial<ColEx>>,
|
||||
},
|
||||
autoSetPlaceHolder: propTypes.bool.def(true),
|
||||
submitOnReset: propTypes.bool,
|
||||
size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
|
||||
// 禁用表单
|
||||
disabled: Boolean as PropType<boolean>,
|
||||
disabled: propTypes.bool,
|
||||
emptySpan: {
|
||||
type: [Number, Object] as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
// 是否显示收起展开按钮
|
||||
showAdvancedButton: { type: Boolean as PropType<boolean>, default: false },
|
||||
showAdvancedButton: propTypes.bool,
|
||||
// 转化时间
|
||||
transformDateFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: (date: any) => {
|
||||
return date._isAMomentObject ? date.format('YYYY-MM-DD HH:mm:ss') : date;
|
||||
return date._isAMomentObject ? date?.format('YYYY-MM-DD HH:mm:ss') : date;
|
||||
},
|
||||
},
|
||||
rulesMessageJoinLabel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
rulesMessageJoinLabel: propTypes.bool.def(true),
|
||||
// 超过3行自动折叠
|
||||
autoAdvancedLine: {
|
||||
type: Number as PropType<number>,
|
||||
default: 3,
|
||||
},
|
||||
autoAdvancedLine: propTypes.number.def(3),
|
||||
|
||||
// 是否显示操作按钮
|
||||
showActionButtonGroup: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showActionButtonGroup: propTypes.bool.def(true),
|
||||
// 操作列Col配置
|
||||
actionColOptions: Object as PropType<ColEx>,
|
||||
actionColOptions: Object as PropType<Partial<ColEx>>,
|
||||
// 显示重置按钮
|
||||
showResetButton: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
// 重置按钮配置
|
||||
resetButtonOptions: Object as PropType<any>,
|
||||
resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 显示确认按钮
|
||||
showSubmitButton: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Object as PropType<any>,
|
||||
submitButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
|
||||
// 自定义重置函数
|
||||
resetFunc: Function as PropType<Fn>,
|
||||
submitFunc: Function as PropType<Fn>,
|
||||
resetFunc: Function as PropType<() => Promise<void>>,
|
||||
submitFunc: Function as PropType<() => Promise<void>>,
|
||||
|
||||
// 以下为默认props
|
||||
hideRequiredMark: Boolean as PropType<boolean>,
|
||||
hideRequiredMark: propTypes.bool,
|
||||
|
||||
labelCol: Object as PropType<ColEx>,
|
||||
labelCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
layout: {
|
||||
type: String as PropType<'horizontal' | 'vertical' | 'inline'>,
|
||||
default: 'horizontal',
|
||||
},
|
||||
layout: propTypes.oneOf(['horizontal', 'vertical', 'inline']).def('horizontal'),
|
||||
tableAction: {
|
||||
type: Object as PropType<TableActionType>,
|
||||
},
|
||||
|
||||
wrapperCol: Object as PropType<any>,
|
||||
wrapperCol: Object as PropType<Partial<ColEx>>,
|
||||
|
||||
colon: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
colon: propTypes.bool,
|
||||
|
||||
labelAlign: String as PropType<string>,
|
||||
labelAlign: propTypes.string,
|
||||
};
|
||||
|
@@ -5,6 +5,7 @@ import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/bu
|
||||
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';
|
||||
|
||||
export type FieldMapToTime = [string, [string, string], string?][];
|
||||
|
||||
@@ -14,8 +15,8 @@ export type Rule = RuleObject & {
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema;
|
||||
values: any;
|
||||
model: any;
|
||||
values: Recordable;
|
||||
model: Recordable;
|
||||
field: string;
|
||||
}
|
||||
|
||||
@@ -25,18 +26,19 @@ export interface ButtonProps extends AntdButtonProps {
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<void>;
|
||||
setFieldsValue: <T>(values: T) => void;
|
||||
resetFields: () => Promise<any>;
|
||||
getFieldsValue: () => any;
|
||||
clearValidate: (name?: string | string[]) => void;
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => void;
|
||||
setProps: (formProps: Partial<FormProps>) => void;
|
||||
removeSchemaByFiled: (field: string | string[]) => void;
|
||||
appendSchemaByField: (schema: FormSchema, prefixField?: string) => void;
|
||||
setFieldsValue: <T>(values: T) => Promise<void>;
|
||||
resetFields: () => Promise<void>;
|
||||
getFieldsValue: () => Recordable;
|
||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>;
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
removeSchemaByFiled: (field: string | string[]) => Promise<void>;
|
||||
appendSchemaByField: (schema: FormSchema, prefixField?: string) => Promise<void>;
|
||||
validateFields: (nameList?: NamePath[]) => Promise<any>;
|
||||
validate: (nameList?: NamePath[]) => Promise<any>;
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => void;
|
||||
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
@@ -44,7 +46,7 @@ export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
export interface FormProps {
|
||||
// layout?: 'vertical' | 'inline' | 'horizontal';
|
||||
// Form value
|
||||
model?: any;
|
||||
model?: Recordable;
|
||||
// The width of all items in the entire form
|
||||
labelWidth?: number | string;
|
||||
// Submit form on reset
|
||||
@@ -55,7 +57,7 @@ export interface FormProps {
|
||||
wrapperCol?: Partial<ColEx>;
|
||||
|
||||
// General row style
|
||||
baseRowStyle?: object;
|
||||
baseRowStyle?: CSSProperties;
|
||||
|
||||
// General col configuration
|
||||
baseColProps?: Partial<ColEx>;
|
||||
@@ -63,7 +65,7 @@ export interface FormProps {
|
||||
// Form configuration rules
|
||||
schemas?: FormSchema[];
|
||||
// Function values used to merge into dynamic control form items
|
||||
mergeDynamicData?: any;
|
||||
mergeDynamicData?: Recordable;
|
||||
// Compact mode for search forms
|
||||
compact?: boolean;
|
||||
// Blank line span
|
||||
@@ -131,8 +133,8 @@ export interface FormSchema {
|
||||
schema: FormSchema;
|
||||
tableAction: TableActionType;
|
||||
formActionType: FormActionType;
|
||||
formModel: any;
|
||||
}) => any)
|
||||
formModel: Recordable;
|
||||
}) => Recordable)
|
||||
| object;
|
||||
// Required
|
||||
required?: boolean;
|
||||
|
@@ -5,12 +5,12 @@ export interface ModalContextProps {
|
||||
redoModalHeight: () => void;
|
||||
}
|
||||
|
||||
const modalContextInjectKey: InjectionKey<ModalContextProps> = Symbol();
|
||||
const key: InjectionKey<ModalContextProps> = Symbol();
|
||||
|
||||
export function createModalContext(context: ModalContextProps) {
|
||||
return createContext<ModalContextProps>(context, modalContextInjectKey);
|
||||
return createContext<ModalContextProps>(context, key);
|
||||
}
|
||||
|
||||
export function useModalContext() {
|
||||
return useContext<ModalContextProps>(modalContextInjectKey);
|
||||
return useContext<ModalContextProps>(key);
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ function extend<T, K>(to: T, _from: K): T & K {
|
||||
return Object.assign(to, _from);
|
||||
}
|
||||
|
||||
export function toObject<T>(arr: Array<T>): Record<string, T> {
|
||||
export function toObject<T>(arr: Array<T>): Recordable<T> {
|
||||
const res = {};
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i]) {
|
||||
|
@@ -221,7 +221,7 @@
|
||||
function handleTableChange(
|
||||
pagination: PaginationProps,
|
||||
// @ts-ignore
|
||||
filters: Partial<Record<string, string[]>>,
|
||||
filters: Partial<Recordable<string[]>>,
|
||||
sorter: SorterResult
|
||||
) {
|
||||
const { clearSelectOnPageChange, sortFn } = unref(getMergeProps);
|
||||
|
@@ -232,7 +232,7 @@ export function renderEditableRow({
|
||||
};
|
||||
}
|
||||
|
||||
export type EditRecordRow<T = { [key: string]: any }> = {
|
||||
export type EditRecordRow<T = Hash<any>> = {
|
||||
editable: boolean;
|
||||
onCancel: Fn;
|
||||
onSubmit: Fn;
|
||||
|
@@ -194,5 +194,5 @@ export interface ColumnProps<T> {
|
||||
* such as slots: { filterIcon: 'XXX'}
|
||||
* @type object
|
||||
*/
|
||||
slots?: Record<string, string>;
|
||||
slots?: Recordable<string>;
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ export function createSimpleTransition(name: string, origin = 'top center 0', mo
|
||||
}
|
||||
export function createJavascriptTransition(
|
||||
name: string,
|
||||
functions: Record<string, any>,
|
||||
functions: Recordable,
|
||||
mode: Mode = 'in-out'
|
||||
) {
|
||||
return defineComponent({
|
||||
|
@@ -54,7 +54,7 @@ export default defineComponent({
|
||||
|
||||
const getWrapStyleRef = computed(
|
||||
(): CSSProperties => {
|
||||
const styles: Record<string, string> = {};
|
||||
const styles: Recordable<string> = {};
|
||||
const height = convertToUnit(props.height);
|
||||
const minHeight = convertToUnit(props.minHeight);
|
||||
const minWidth = convertToUnit(props.minWidth);
|
||||
|
@@ -40,7 +40,7 @@ const pattern = {
|
||||
} as const;
|
||||
|
||||
function parseStyle(style: string) {
|
||||
const styleMap: Dictionary<any> = {};
|
||||
const styleMap: Recordable = {};
|
||||
|
||||
for (const s of style.split(pattern.styleList)) {
|
||||
let [key, val] = s.split(pattern.styleProp);
|
||||
@@ -161,8 +161,8 @@ export function mergeClasses(target: any, source: any) {
|
||||
}
|
||||
|
||||
export function mergeListeners(
|
||||
target: { [key: string]: Function | Function[] } | undefined,
|
||||
source: { [key: string]: Function | Function[] } | undefined
|
||||
target: Indexable<Function | Function[]> | undefined,
|
||||
source: Indexable<Function | Function[]> | undefined
|
||||
) {
|
||||
if (!target) return source;
|
||||
if (!source) return target;
|
||||
|
@@ -154,7 +154,7 @@ function rippler({
|
||||
setTimeout(() => {
|
||||
let clearPosition = true;
|
||||
for (let i = 0; i < el.childNodes.length; i++) {
|
||||
if ((el.childNodes[i] as any).className === 'ripple-container') {
|
||||
if ((el.childNodes[i] as Recordable).className === 'ripple-container') {
|
||||
clearPosition = false;
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ function rippler({
|
||||
clearRipple();
|
||||
}
|
||||
|
||||
(el as any).setBackground = (bgColor: string) => {
|
||||
(el as Recordable).setBackground = (bgColor: string) => {
|
||||
if (!bgColor) {
|
||||
return;
|
||||
}
|
||||
@@ -181,8 +181,8 @@ function rippler({
|
||||
};
|
||||
}
|
||||
|
||||
function setProps(modifiers: { [key: string]: any }, props: Record<string, any>) {
|
||||
modifiers.forEach((item: any) => {
|
||||
function setProps(modifiers: Hash<any>, props: Recordable) {
|
||||
modifiers.forEach((item: Recordable) => {
|
||||
if (isNaN(Number(item))) props.event = item;
|
||||
else props.transition = item;
|
||||
});
|
||||
|
35
src/hooks/component/useFormItem.ts
Normal file
35
src/hooks/component/useFormItem.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { UnwrapRef } from 'vue';
|
||||
import { reactive, readonly, computed, getCurrentInstance } from 'vue';
|
||||
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
export function useRuleFormItem<T extends Indexable>(
|
||||
props: T,
|
||||
key: keyof T = 'value',
|
||||
changeEvent = 'change'
|
||||
) {
|
||||
const instance = getCurrentInstance();
|
||||
const emit = instance?.emit;
|
||||
|
||||
const innerState = reactive({
|
||||
value: props[key],
|
||||
});
|
||||
|
||||
const defaultState = readonly(innerState);
|
||||
|
||||
const setState = (val: UnwrapRef<T[keyof T]>) => {
|
||||
innerState.value = val as T[keyof T];
|
||||
};
|
||||
const state = computed({
|
||||
get() {
|
||||
return innerState.value;
|
||||
},
|
||||
set(value) {
|
||||
if (isEqual(value, defaultState.value)) return;
|
||||
innerState.value = value as T[keyof T];
|
||||
emit?.(changeEvent, value);
|
||||
},
|
||||
});
|
||||
|
||||
return [state, setState, defaultState];
|
||||
}
|
39
src/hooks/core/useAttrs.ts
Normal file
39
src/hooks/core/useAttrs.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { getCurrentInstance, reactive, shallowRef, watchEffect } from 'vue';
|
||||
|
||||
interface Params {
|
||||
excludeListeners?: boolean;
|
||||
excludeKeys?: string[];
|
||||
}
|
||||
|
||||
const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
|
||||
const LISTENER_PREFIX = /^on[A-Z]/;
|
||||
|
||||
export function entries<T>(obj: Hash<T>): [string, T][] {
|
||||
return Object.keys(obj).map((key: string) => [key, obj[key]]);
|
||||
}
|
||||
|
||||
export function useAttrs(params: Params = {}) {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) return {};
|
||||
|
||||
const { excludeListeners = false, excludeKeys = [] } = params;
|
||||
const attrs = shallowRef({});
|
||||
const allExcludeKeys = excludeKeys.concat(DEFAULT_EXCLUDE_KEYS);
|
||||
|
||||
// Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance
|
||||
instance.attrs = reactive(instance.attrs);
|
||||
|
||||
watchEffect(() => {
|
||||
const res = entries(instance.attrs).reduce((acm, [key, val]) => {
|
||||
if (!allExcludeKeys.includes(key) && !(excludeListeners && LISTENER_PREFIX.test(key))) {
|
||||
acm[key] = val;
|
||||
}
|
||||
|
||||
return acm;
|
||||
}, {} as Hash<any>);
|
||||
|
||||
attrs.value = res;
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
@@ -23,7 +23,7 @@ export type EventOption = {
|
||||
const defaultEvents: keyEvent[] = ['keydown'];
|
||||
|
||||
// 键盘事件 keyCode 别名
|
||||
const aliasKeyCodeMap: Record<string, number | number[]> = {
|
||||
const aliasKeyCodeMap: Recordable<number | number[]> = {
|
||||
esc: 27,
|
||||
tab: 9,
|
||||
enter: 13,
|
||||
@@ -36,7 +36,7 @@ const aliasKeyCodeMap: Record<string, number | number[]> = {
|
||||
};
|
||||
|
||||
// 键盘事件 key 别名
|
||||
const aliasKeyMap: Record<string, string | string[]> = {
|
||||
const aliasKeyMap: Recordable<string | string[]> = {
|
||||
esc: 'Escape',
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
@@ -50,7 +50,7 @@ const aliasKeyMap: Record<string, string | string[]> = {
|
||||
};
|
||||
|
||||
// 修饰键
|
||||
const modifierKey: Record<string, (event: KeyboardEvent) => boolean> = {
|
||||
const modifierKey: Recordable<(event: KeyboardEvent) => boolean> = {
|
||||
ctrl: (event: KeyboardEvent) => event.ctrlKey,
|
||||
shift: (event: KeyboardEvent) => event.shiftKey,
|
||||
alt: (event: KeyboardEvent) => event.altKey,
|
||||
|
@@ -24,7 +24,7 @@ export function useI18n(namespace?: string) {
|
||||
|
||||
return {
|
||||
...methods,
|
||||
t: (key: string, ...arg: any) => {
|
||||
t: (key: string, ...arg: any): string => {
|
||||
if (!key) return '';
|
||||
return t(getKey(key), ...(arg as Parameters<typeof t>));
|
||||
},
|
||||
|
@@ -13,7 +13,7 @@ import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
interface DefaultContext {
|
||||
Component: FunctionalComponent & { type: { [key: string]: any } };
|
||||
Component: FunctionalComponent & { type: Indexable };
|
||||
route: RouteLocation;
|
||||
}
|
||||
|
||||
|
@@ -40,7 +40,7 @@ export function createPermissionGuard(router: Router) {
|
||||
return;
|
||||
}
|
||||
// redirect login page
|
||||
const redirectData: { path: string; replace: boolean; query?: { [key: string]: string } } = {
|
||||
const redirectData: { path: string; replace: boolean; query?: Indexable<string> } = {
|
||||
path: LOGIN_PATH,
|
||||
replace: true,
|
||||
};
|
||||
|
@@ -48,7 +48,7 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
component?: Component | string;
|
||||
components?: Component;
|
||||
children?: AppRouteRecordRaw[];
|
||||
props?: Record<string, any>;
|
||||
props?: Recordable;
|
||||
fullPath?: string;
|
||||
}
|
||||
export interface MenuTag {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
export const HEADER_PRESET_BG_COLOR_LIST: string[] = [
|
||||
'#ffffff',
|
||||
'#009688',
|
||||
'#18bc9c',
|
||||
'#5172DC',
|
||||
'#1E9FFF',
|
||||
'#018ffb',
|
||||
'#409eff',
|
||||
|
10
src/types/global.d.ts
vendored
10
src/types/global.d.ts
vendored
@@ -15,18 +15,20 @@ declare function parseInt(s: string | number, radix?: number): number;
|
||||
|
||||
declare function parseFloat(string: string | number): number;
|
||||
|
||||
declare type Dictionary<T> = Record<string, T>;
|
||||
|
||||
declare type Nullable<T> = T | null;
|
||||
|
||||
declare type NonNullable<T> = T extends null | undefined ? never : T;
|
||||
|
||||
declare type RefType<T> = T | null;
|
||||
|
||||
declare type CustomizedHTMLElement<T> = HTMLElement & T;
|
||||
|
||||
declare type Indexable<T = any> = {
|
||||
declare type Indexable<T extends any = any> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
|
||||
declare type Recordable<T extends any = any> = Record<string, T>;
|
||||
|
||||
declare type Hash<T> = Indexable<T>;
|
||||
|
||||
declare type DeepPartial<T> = {
|
||||
@@ -59,3 +61,5 @@ declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
|
||||
declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null;
|
||||
|
||||
declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
|
||||
|
||||
type IsSame<A, B> = A | B extends A & B ? true : false;
|
||||
|
@@ -7,8 +7,8 @@ const ls = createStorage(localStorage);
|
||||
const ss = createStorage();
|
||||
|
||||
interface CacheStore {
|
||||
local: Record<string, any>;
|
||||
session: Record<string, any>;
|
||||
local: Recordable;
|
||||
session: Recordable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -35,7 +35,7 @@ export function extendSlots(slots: Slots, excludeKeys: string[] = []) {
|
||||
}
|
||||
|
||||
// Get events on attrs
|
||||
export function getListeners(attrs: Record<string, unknown>) {
|
||||
export function getListeners(attrs: Recordable<unknown>) {
|
||||
const listeners: any = {};
|
||||
Object.keys(attrs).forEach((key) => {
|
||||
if (/^on/.test(key)) {
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
reactive,
|
||||
ComponentInternalInstance,
|
||||
} from 'vue';
|
||||
import { error } from '../log';
|
||||
|
||||
export function explicitComputed<T, S>(source: WatchSource<S>, fn: () => T) {
|
||||
const v = reactive<any>({ value: fn() });
|
||||
@@ -39,6 +40,6 @@ export function tryTsxEmit<T extends any = ComponentInternalInstance>(
|
||||
|
||||
export function isInSetup() {
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('Please put useForm function in the setup function!');
|
||||
error('Please put useForm function in the setup function!');
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ export interface Result<T = any> {
|
||||
// multipart/form-data:上传文件
|
||||
export interface UploadFileParams {
|
||||
// 其他参数
|
||||
data?: { [key: string]: any };
|
||||
data?: Indexable;
|
||||
// 文件参数的接口字段名
|
||||
name?: string;
|
||||
// 文件
|
||||
|
@@ -38,7 +38,7 @@ export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||
return url;
|
||||
}
|
||||
|
||||
export function deepMerge<T = any>(src: any, target: any): T {
|
||||
export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
|
||||
let key: string;
|
||||
for (key in target) {
|
||||
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
|
||||
|
@@ -3,3 +3,7 @@ const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
export function warn(message: string) {
|
||||
console.warn(`[${projectName} warn]:${message}`);
|
||||
}
|
||||
|
||||
export function error(message: string) {
|
||||
throw new Error(`[${projectName} error]:${message}`);
|
||||
}
|
||||
|
@@ -84,12 +84,15 @@
|
||||
required: true,
|
||||
// @ts-ignore
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('值不能为空');
|
||||
}
|
||||
if (value === '1') {
|
||||
return Promise.reject('值不能为1');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
Reference in New Issue
Block a user