mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-28 05:39:34 +08:00
initial commit
This commit is contained in:
7
src/components/Form/index.ts
Normal file
7
src/components/Form/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as 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';
|
463
src/components/Form/src/BasicForm.vue
Normal file
463
src/components/Form/src/BasicForm.vue
Normal file
@@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<Form v-bind="$attrs" ref="formElRef" :model="formModel">
|
||||
<Row :class="getProps.compact ? 'compact-form-row' : ''">
|
||||
<slot name="formHeader" />
|
||||
<template v-for="schema in getSchema" :key="schema.field">
|
||||
<FormItem
|
||||
:schema="schema"
|
||||
:formProps="getProps"
|
||||
:allDefaultValues="getAllDefaultValues"
|
||||
:formModel="formModel"
|
||||
>
|
||||
<template v-slot:[item] v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" />
|
||||
</template>
|
||||
</FormItem>
|
||||
</template>
|
||||
<FormAction
|
||||
v-bind="{ ...getActionPropsRef, ...advanceState }"
|
||||
@toggle-advanced="handleToggleAdvanced"
|
||||
/>
|
||||
<slot name="formFooter" />
|
||||
</Row>
|
||||
</Form>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { FormActionType, FormProps, FormSchema } from './types/form';
|
||||
import type { Form as FormType, ValidateFields } from 'ant-design-vue/types/form/form';
|
||||
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
unref,
|
||||
toRaw,
|
||||
watch,
|
||||
toRef,
|
||||
onMounted,
|
||||
} from 'vue';
|
||||
import { Form, Row } from 'ant-design-vue';
|
||||
import FormItem from './FormItem';
|
||||
import { basicProps } from './props';
|
||||
import { deepMerge, unique } from '/@/utils';
|
||||
import FormAction from './FormAction';
|
||||
|
||||
import { dateItemType } from './helper';
|
||||
import moment from 'moment';
|
||||
import { isArray, isBoolean, isFunction, isNumber, isObject, isString } from '/@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
|
||||
import { useThrottle } from '/@/hooks/core/useThrottle';
|
||||
import { useFormValues } from './hooks/useFormValues';
|
||||
import type { ColEx } from './types';
|
||||
import { NamePath } from 'ant-design-vue/types/form/form-item';
|
||||
const BASIC_COL_LEN = 24;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicForm',
|
||||
inheritAttrs: false,
|
||||
components: { FormItem, Form, Row, FormAction },
|
||||
props: basicProps,
|
||||
emits: ['advanced-change', 'reset', 'submit', 'register'],
|
||||
setup(props, { emit }) {
|
||||
let formModel = reactive({});
|
||||
const advanceState = reactive({
|
||||
isAdvanced: true,
|
||||
hideAdvanceBtn: false,
|
||||
isLoad: false,
|
||||
actionSpan: 6,
|
||||
});
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
const schemaRef = ref<FormSchema[] | null>(null);
|
||||
const formElRef = ref<Nullable<FormType>>(null);
|
||||
|
||||
const getMergePropsRef = computed(
|
||||
(): FormProps => {
|
||||
return deepMerge(toRaw(props), unref(propsRef));
|
||||
}
|
||||
);
|
||||
// 获取表单基本配置
|
||||
const getProps = computed(
|
||||
(): FormProps => {
|
||||
const resetAction = {
|
||||
onClick: resetFields,
|
||||
};
|
||||
const submitAction = {
|
||||
onClick: handleSubmit,
|
||||
};
|
||||
return {
|
||||
...unref(getMergePropsRef),
|
||||
resetButtonOptions: deepMerge(
|
||||
resetAction,
|
||||
unref(getMergePropsRef).resetButtonOptions || {}
|
||||
) as any,
|
||||
submitButtonOptions: deepMerge(
|
||||
submitAction,
|
||||
unref(getMergePropsRef).submitButtonOptions || {}
|
||||
) as any,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const getActionPropsRef = computed(() => {
|
||||
const {
|
||||
resetButtonOptions,
|
||||
submitButtonOptions,
|
||||
showActionButtonGroup,
|
||||
showResetButton,
|
||||
showSubmitButton,
|
||||
showAdvancedButton,
|
||||
actionColOptions,
|
||||
} = unref(getProps);
|
||||
return {
|
||||
resetButtonOptions,
|
||||
submitButtonOptions,
|
||||
show: showActionButtonGroup,
|
||||
showResetButton,
|
||||
showSubmitButton,
|
||||
showAdvancedButton,
|
||||
actionColOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||
for (const schema of schemas) {
|
||||
const { defaultValue, component } = schema;
|
||||
if (defaultValue && dateItemType.includes(component!)) {
|
||||
schema.defaultValue = moment(defaultValue);
|
||||
}
|
||||
}
|
||||
return schemas as FormSchema[];
|
||||
});
|
||||
|
||||
const getAllDefaultValues = computed(() => {
|
||||
const schemas = unref(getSchema);
|
||||
const obj: any = {};
|
||||
schemas.forEach((item) => {
|
||||
if (item.defaultValue) {
|
||||
obj[item.field] = item.defaultValue;
|
||||
(formModel as any)[item.field] = item.defaultValue;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
const getEmptySpanRef = computed((): number => {
|
||||
if (!advanceState.isAdvanced) {
|
||||
return 0;
|
||||
}
|
||||
const emptySpan = unref(getMergePropsRef).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 { realWidthRef, screenEnum, screenRef } = useBreakpoint();
|
||||
const [throttleUpdateAdvanced] = useThrottle(updateAdvanced, 30, { immediate: true });
|
||||
watch(
|
||||
[() => unref(getSchema), () => advanceState.isAdvanced, () => unref(realWidthRef)],
|
||||
() => {
|
||||
const { showAdvancedButton } = unref(getProps);
|
||||
if (showAdvancedButton) {
|
||||
throttleUpdateAdvanced();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
function updateAdvanced() {
|
||||
let itemColSum = 0;
|
||||
let realItemColSum = 0;
|
||||
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: {
|
||||
...getAllDefaultValues,
|
||||
...formModel,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (isShow && colProps) {
|
||||
const { itemColSum: sum, isAdvanced } = getAdvanced(colProps, itemColSum);
|
||||
|
||||
itemColSum = sum || 0;
|
||||
if (isAdvanced) {
|
||||
realItemColSum = itemColSum;
|
||||
}
|
||||
schema.isAdvanced = isAdvanced;
|
||||
}
|
||||
}
|
||||
advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpanRef);
|
||||
getAdvanced(props.actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);
|
||||
emit('advanced-change');
|
||||
}
|
||||
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) {
|
||||
// 小于等于2行时,不显示收起展开按钮
|
||||
advanceState.hideAdvanceBtn = true;
|
||||
advanceState.isAdvanced = true;
|
||||
} else if (
|
||||
itemColSum > BASIC_COL_LEN * 2 &&
|
||||
itemColSum <= BASIC_COL_LEN * (props.autoAdvancedLine || 3)
|
||||
) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
|
||||
// 大于3行默认收起
|
||||
} else if (!advanceState.isLoad) {
|
||||
advanceState.isLoad = true;
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
}
|
||||
if (itemColSum > BASIC_COL_LEN) {
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
} else {
|
||||
// 第一行始终显示
|
||||
return { isAdvanced: true, itemColSum };
|
||||
}
|
||||
}
|
||||
|
||||
async function resetFields(): Promise<any> {
|
||||
const { resetFunc } = unref(getProps);
|
||||
resetFunc && isFunction(resetFunc) && (await resetFunc());
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
Object.keys(formModel).forEach((key) => {
|
||||
(formModel as any)[key] = undefined;
|
||||
});
|
||||
const values = formEl.resetFields();
|
||||
emit('reset', toRaw(formModel));
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 设置表单值
|
||||
*/
|
||||
async function setFieldsValue(values: any): Promise<void> {
|
||||
const fields = unref(getSchema)
|
||||
.map((item) => item.field)
|
||||
.filter(Boolean);
|
||||
const formEl = unref(formElRef);
|
||||
Object.keys(values).forEach((key) => {
|
||||
const element = values[key];
|
||||
if (fields.includes(key) && element !== undefined && element !== null) {
|
||||
// 时间
|
||||
(formModel as any)[key] = itemIsDateType(key) ? moment(element) : element;
|
||||
if (formEl) {
|
||||
formEl.validateFields([key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 表单提交
|
||||
*/
|
||||
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 formEl.validate();
|
||||
const res = handleFormValues(values);
|
||||
emit('submit', res);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 根据字段名删除
|
||||
*/
|
||||
function removeSchemaByFiled(fields: string | string[]): void {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
let fieldList: string[] = fields as string[];
|
||||
if (isString(fields)) {
|
||||
fieldList = [fields];
|
||||
}
|
||||
for (const field of fieldList) {
|
||||
_removeSchemaByFiled(field, schemaList);
|
||||
}
|
||||
schemaRef.value = schemaList as any;
|
||||
}
|
||||
/**
|
||||
* @description: 根据字段名删除
|
||||
*/
|
||||
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void {
|
||||
if (isString(field)) {
|
||||
const index = schemaList.findIndex((schema) => schema.field === field);
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @description: 往某个字段后面插入,如果没有插入最后一个
|
||||
*/
|
||||
function appendSchemaByField(schema: FormSchema, prefixField?: string) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField);
|
||||
const hasInList = schemaList.find((item) => item.field === schema.field);
|
||||
|
||||
if (hasInList) {
|
||||
return;
|
||||
}
|
||||
if (!prefixField || index === -1) {
|
||||
schemaList.push(schema);
|
||||
schemaRef.value = schemaList as any;
|
||||
return;
|
||||
}
|
||||
if (index !== -1) {
|
||||
schemaList.splice(index + 1, 0, schema);
|
||||
}
|
||||
schemaRef.value = schemaList as any;
|
||||
}
|
||||
|
||||
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) => Reflect.has(item, 'field') && item.field);
|
||||
if (!hasField) {
|
||||
throw new Error('Must pass in the `field` field!');
|
||||
}
|
||||
const schema: FormSchema[] = [];
|
||||
updateData.forEach((item) => {
|
||||
unref(getSchema).forEach((val) => {
|
||||
if (val.field === item.field) {
|
||||
const newScheam = deepMerge(val, item);
|
||||
schema.push(newScheam as FormSchema);
|
||||
} else {
|
||||
schema.push(val);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
schemaRef.value = unique(schema, 'field') as any;
|
||||
}
|
||||
|
||||
function handleToggleAdvanced() {
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
}
|
||||
|
||||
const handleFormValues = useFormValues(
|
||||
toRef(props, 'transformDateFunc'),
|
||||
toRef(props, 'fieldMapToTime')
|
||||
);
|
||||
function getFieldsValue(): any {
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return;
|
||||
return handleFormValues(toRaw(unref(formModel)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否是时间
|
||||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some((item) => {
|
||||
return item.field === key ? dateItemType.includes(item.component!) : false;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description:设置表单
|
||||
*/
|
||||
function setProps(formProps: Partial<FormProps>): void {
|
||||
const mergeProps = deepMerge(unref(propsRef) || {}, formProps);
|
||||
propsRef.value = mergeProps;
|
||||
}
|
||||
|
||||
function validateFields(nameList?: NamePath[] | undefined) {
|
||||
if (!formElRef.value) return;
|
||||
return formElRef.value.validateFields(nameList);
|
||||
}
|
||||
function validate(nameList?: NamePath[] | undefined) {
|
||||
if (!formElRef.value) return;
|
||||
return formElRef.value.validate(nameList);
|
||||
}
|
||||
|
||||
function clearValidate(name: string | string[]) {
|
||||
if (!formElRef.value) return;
|
||||
formElRef.value.clearValidate(name);
|
||||
}
|
||||
|
||||
const methods: Partial<FormActionType> = {
|
||||
getFieldsValue,
|
||||
setFieldsValue,
|
||||
resetFields,
|
||||
updateSchema,
|
||||
setProps,
|
||||
removeSchemaByFiled,
|
||||
appendSchemaByField,
|
||||
clearValidate,
|
||||
validateFields: validateFields as ValidateFields,
|
||||
validate: validate as ValidateFields,
|
||||
};
|
||||
onMounted(() => {
|
||||
emit('register', methods);
|
||||
});
|
||||
return {
|
||||
handleToggleAdvanced,
|
||||
formModel,
|
||||
getActionPropsRef,
|
||||
getAllDefaultValues,
|
||||
advanceState,
|
||||
getProps,
|
||||
formElRef,
|
||||
getSchema,
|
||||
...methods,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
141
src/components/Form/src/FormAction.tsx
Normal file
141
src/components/Form/src/FormAction.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { defineComponent, unref, computed, PropType } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import type { ColEx } from './types/index';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import Button from '/@/components/Button/index.vue';
|
||||
import { UpOutlined, DownOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicFormAction',
|
||||
emits: ['toggle-advanced'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showResetButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showSubmitButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showAdvancedButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
resetButtonOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
submitButtonOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
actionColOptions: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
actionSpan: {
|
||||
type: Number,
|
||||
default: 6,
|
||||
},
|
||||
isAdvanced: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hideAdvanceBtn: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { slots, emit }) {
|
||||
const getResetBtnOptionsRef = computed(() => {
|
||||
return {
|
||||
text: '重置',
|
||||
...props.resetButtonOptions,
|
||||
};
|
||||
});
|
||||
const getSubmitBtnOptionsRef = computed(() => {
|
||||
return {
|
||||
text: '查询',
|
||||
// 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,
|
||||
...actionColOptions,
|
||||
...advancedSpanObj,
|
||||
};
|
||||
return actionColOpt;
|
||||
});
|
||||
|
||||
function toggleAdvanced() {
|
||||
emit('toggle-advanced');
|
||||
}
|
||||
return () => {
|
||||
if (!props.show) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
showAdvancedButton,
|
||||
hideAdvanceBtn,
|
||||
isAdvanced,
|
||||
showResetButton,
|
||||
showSubmitButton,
|
||||
} = props;
|
||||
return (
|
||||
<>
|
||||
<Col {...unref(actionColOpt)} style={{ textAlign: 'right' }}>
|
||||
{() => (
|
||||
<Form.Item>
|
||||
{() => (
|
||||
<>
|
||||
{getSlot(slots, 'advanceBefore')}
|
||||
{showAdvancedButton && !hideAdvanceBtn && (
|
||||
<Button type="default" class="mr-2" onClick={toggleAdvanced}>
|
||||
{() => (
|
||||
<>
|
||||
{isAdvanced ? '收起' : '展开'}
|
||||
{isAdvanced ? (
|
||||
<UpOutlined class="advanced-icon" />
|
||||
) : (
|
||||
<DownOutlined class="advanced-icon" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{getSlot(slots, 'resetBefore')}
|
||||
{showResetButton && (
|
||||
<Button type="default" class="mr-2" {...unref(getResetBtnOptionsRef)}>
|
||||
{() => unref(getResetBtnOptionsRef).text}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{getSlot(slots, 'submitBefore')}
|
||||
{showSubmitButton && (
|
||||
<Button type="primary" {...unref(getSubmitBtnOptionsRef)}>
|
||||
{() => unref(getSubmitBtnOptionsRef).text}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{getSlot(slots, 'submitAfter')}
|
||||
</>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
267
src/components/Form/src/FormItem.tsx
Normal file
267
src/components/Form/src/FormItem.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
import { defineComponent, computed, unref, toRef } from 'vue';
|
||||
import { Form, Col } from 'ant-design-vue';
|
||||
import { componentMap } from './componentMap';
|
||||
|
||||
import type { PropType } from 'vue';
|
||||
import type { FormProps } from './types/form';
|
||||
import type { FormSchema } from './types/form';
|
||||
import { isBoolean, isFunction } from '/@/utils/is';
|
||||
import { useItemLabelWidth } from './hooks/useLabelWidth';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
import { BasicHelp } from '/@/components/Basic';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { upperFirst, cloneDeep } from 'lodash-es';
|
||||
import { ValidationRule } from 'ant-design-vue/types/form/form';
|
||||
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<any>,
|
||||
default: {},
|
||||
},
|
||||
formModel: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const itemLabelWidthRef = useItemLabelWidth(toRef(props, 'schema'), toRef(props, 'formProps'));
|
||||
|
||||
const getValuesRef = computed(() => {
|
||||
const { allDefaultValues, formModel, schema } = props;
|
||||
const { mergeDynamicData } = props.formProps;
|
||||
return {
|
||||
field: schema.field,
|
||||
model: formModel,
|
||||
values: {
|
||||
...mergeDynamicData,
|
||||
...allDefaultValues,
|
||||
...formModel,
|
||||
},
|
||||
schema: schema,
|
||||
};
|
||||
});
|
||||
const getShowRef = computed(() => {
|
||||
const { show, ifShow, isAdvanced } = props.schema;
|
||||
const { showAdvancedButton } = props.formProps;
|
||||
const itemIsAdvanced = showAdvancedButton ? !!isAdvanced : true;
|
||||
let isShow = true;
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(show)) {
|
||||
isShow = show;
|
||||
}
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(show)) {
|
||||
isShow = show(unref(getValuesRef));
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(unref(getValuesRef));
|
||||
}
|
||||
isShow = isShow && itemIsAdvanced;
|
||||
return { isShow, isIfShow };
|
||||
});
|
||||
|
||||
const getDisableRef = computed(() => {
|
||||
const { disabled: globDisabled } = props.formProps;
|
||||
const { dynamicDisabled } = props.schema;
|
||||
let disabled = !!globDisabled;
|
||||
if (isBoolean(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled;
|
||||
}
|
||||
|
||||
if (isFunction(dynamicDisabled)) {
|
||||
disabled = dynamicDisabled(unref(getValuesRef));
|
||||
}
|
||||
|
||||
return disabled;
|
||||
});
|
||||
|
||||
function handleRules(): ValidationRule[] {
|
||||
const {
|
||||
rules: defRules = [],
|
||||
component,
|
||||
rulesMessageJoinLabel,
|
||||
label,
|
||||
dynamicRules,
|
||||
} = props.schema;
|
||||
|
||||
if (isFunction(dynamicRules)) {
|
||||
return dynamicRules(unref(getValuesRef));
|
||||
}
|
||||
|
||||
const rules: ValidationRule[] = cloneDeep(defRules);
|
||||
const requiredRuleIndex: number = rules.findIndex(
|
||||
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator')
|
||||
);
|
||||
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
|
||||
if (requiredRuleIndex !== -1) {
|
||||
const rule = rules[requiredRuleIndex];
|
||||
if (rule.required && component) {
|
||||
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';
|
||||
}
|
||||
if (component.includes('RangePicker')) {
|
||||
rule.type = 'array';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最大输入长度规则校验
|
||||
const characterInx = rules.findIndex((val) => val.max);
|
||||
if (characterInx !== -1 && !rules[characterInx].validator) {
|
||||
rules[characterInx].message =
|
||||
rules[characterInx].message || `字符数应小于${rules[characterInx].max}位`;
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
function renderComponent() {
|
||||
const {
|
||||
componentProps,
|
||||
renderComponentContent,
|
||||
component,
|
||||
field,
|
||||
changeEvent = 'change',
|
||||
} = props.schema;
|
||||
|
||||
const isCheck = component && ['Switch'].includes(component);
|
||||
|
||||
const eventKey = `on${upperFirst(changeEvent)}`;
|
||||
const on = {
|
||||
[eventKey]: (e: any) => {
|
||||
if (propsData[eventKey]) {
|
||||
propsData[eventKey](e);
|
||||
}
|
||||
if (e && e.target) {
|
||||
(props.formModel as any)[field] = e.target.value;
|
||||
} else {
|
||||
(props.formModel as any)[field] = e;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const Comp = componentMap.get(component);
|
||||
|
||||
const { autoSetPlaceHolder, size } = props.formProps;
|
||||
const propsData: any = {
|
||||
allowClear: true,
|
||||
getPopupContainer: (trigger: Element) => trigger.parentNode,
|
||||
size,
|
||||
...componentProps,
|
||||
disabled: unref(getDisableRef),
|
||||
};
|
||||
|
||||
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
|
||||
let placeholder;
|
||||
// RangePicker place为数组
|
||||
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
|
||||
placeholder =
|
||||
(componentProps && componentProps.placeholder) || createPlaceholderMessage(component);
|
||||
}
|
||||
propsData.placeholder = placeholder;
|
||||
propsData.codeField = field;
|
||||
propsData.formValues = unref(getValuesRef);
|
||||
|
||||
const bindValue = {
|
||||
[isCheck ? 'checked' : 'value']: (props.formModel as any)[field],
|
||||
};
|
||||
return (
|
||||
<Comp {...propsData} {...on} {...bindValue}>
|
||||
{{
|
||||
...(renderComponentContent
|
||||
? renderComponentContent(unref(getValuesRef))
|
||||
: {
|
||||
default: () => '',
|
||||
}),
|
||||
}}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLabelHelpMessage() {
|
||||
const { label, helpMessage, helpComponentProps } = props.schema;
|
||||
if (!helpMessage || (Array.isArray(helpMessage) && helpMessage.length === 0)) {
|
||||
return label;
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
{label}
|
||||
<BasicHelp class="mx-1" text={helpMessage} {...helpComponentProps} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field } = props.schema;
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthRef);
|
||||
const { colon } = props.formProps;
|
||||
const getContent = () => {
|
||||
return slot
|
||||
? getSlot(slots, slot)
|
||||
: render
|
||||
? render(unref(getValuesRef))
|
||||
: renderComponent();
|
||||
};
|
||||
return (
|
||||
<Form.Item
|
||||
name={field}
|
||||
colon={colon}
|
||||
{...itemProps}
|
||||
label={renderLabelHelpMessage()}
|
||||
rules={handleRules()}
|
||||
labelCol={labelCol}
|
||||
wrapperCol={wrapperCol}
|
||||
>
|
||||
{() => getContent()}
|
||||
</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 } = unref(getShowRef);
|
||||
|
||||
const getContent = () => {
|
||||
return colSlot
|
||||
? getSlot(slots, colSlot)
|
||||
: renderColContent
|
||||
? renderColContent(unref(getValuesRef))
|
||||
: renderItem();
|
||||
};
|
||||
return (
|
||||
isIfShow && (
|
||||
<Col {...realColProps} class={!isShow ? 'hidden' : ''}>
|
||||
{() => getContent()}
|
||||
</Col>
|
||||
)
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
59
src/components/Form/src/componentMap.ts
Normal file
59
src/components/Form/src/componentMap.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Component } from 'vue';
|
||||
/**
|
||||
* 组件列表,在这里注册才可以在表单使用
|
||||
*/
|
||||
import {
|
||||
Input,
|
||||
Select,
|
||||
Radio,
|
||||
Checkbox,
|
||||
AutoComplete,
|
||||
Cascader,
|
||||
DatePicker,
|
||||
InputNumber,
|
||||
Switch,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Transfer,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { ComponentType } from './types/index';
|
||||
|
||||
const componentMap = new Map<ComponentType, any>();
|
||||
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputGroup', Input.Group);
|
||||
componentMap.set('InputPassword', Input.Password);
|
||||
componentMap.set('InputSearch', Input.Search);
|
||||
componentMap.set('InputTextArea', Input.TextArea);
|
||||
componentMap.set('InputNumber', InputNumber);
|
||||
componentMap.set('AutoComplete', AutoComplete);
|
||||
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('SelectOptGroup', Select.OptGroup);
|
||||
componentMap.set('SelectOption', Select.Option);
|
||||
componentMap.set('TreeSelect', TreeSelect);
|
||||
componentMap.set('Transfer', Transfer);
|
||||
componentMap.set('Radio', Radio);
|
||||
componentMap.set('Switch', Switch);
|
||||
componentMap.set('RadioButton', Radio.Button);
|
||||
componentMap.set('RadioGroup', Radio.Group);
|
||||
componentMap.set('Checkbox', Checkbox);
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group);
|
||||
componentMap.set('Cascader', Cascader);
|
||||
|
||||
componentMap.set('DatePicker', DatePicker);
|
||||
componentMap.set('MonthPicker', DatePicker.MonthPicker);
|
||||
componentMap.set('RangePicker', DatePicker.RangePicker);
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker);
|
||||
componentMap.set('TimePicker', TimePicker);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
30
src/components/Form/src/helper.ts
Normal file
30
src/components/Form/src/helper.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ComponentType } from './types/index';
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component.includes('Input') || component.includes('Complete')) {
|
||||
return '请输入';
|
||||
}
|
||||
if (component.includes('Picker') && !component.includes('Range')) {
|
||||
return '请选择';
|
||||
}
|
||||
if (
|
||||
component.includes('Select') ||
|
||||
component.includes('Cascader') ||
|
||||
component.includes('Checkbox') ||
|
||||
component.includes('Radio') ||
|
||||
component.includes('Switch')
|
||||
) {
|
||||
// return `请选择${label}`;
|
||||
return '请选择';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
function genType() {
|
||||
return ['DatePicker', 'MonthPicker', 'RangePicker', 'WeekPicker', 'TimePicker'];
|
||||
}
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
export const dateItemType = genType();
|
10
src/components/Form/src/hooks/useComponentRegister.ts
Normal file
10
src/components/Form/src/hooks/useComponentRegister.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper';
|
||||
import { add, del } from '../componentMap';
|
||||
|
||||
import { ComponentType } from '../types/index';
|
||||
export function useComponentRegister(compName: ComponentType, comp: any) {
|
||||
add(compName, comp);
|
||||
tryOnUnmounted(() => {
|
||||
del(compName);
|
||||
});
|
||||
}
|
69
src/components/Form/src/hooks/useForm.ts
Normal file
69
src/components/Form/src/hooks/useForm.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ref, onUnmounted, unref } from 'vue';
|
||||
|
||||
import { isInSetup } from '/@/utils/helper/vueHelper';
|
||||
|
||||
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import type { NamePath } from 'ant-design-vue/types/form/form-item';
|
||||
import type { ValidateFields } from 'ant-design-vue/types/form/form';
|
||||
|
||||
export function useForm(props?: Partial<FormProps>): UseFormReturnType {
|
||||
isInSetup();
|
||||
const formRef = ref<FormActionType | null>(null);
|
||||
const loadedRef = ref<boolean | null>(false);
|
||||
function getForm() {
|
||||
const form = unref(formRef);
|
||||
if (!form) {
|
||||
throw new Error('formRef is Null');
|
||||
}
|
||||
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;
|
||||
props && instance.setProps(props);
|
||||
loadedRef.value = true;
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
setProps: (formProps: Partial<FormProps>) => {
|
||||
getForm().setProps(formProps);
|
||||
},
|
||||
updateSchema: (data: Partial<FormSchema> | Partial<FormSchema>[]) => {
|
||||
getForm().updateSchema(data);
|
||||
},
|
||||
clearValidate: (name?: string | string[]) => {
|
||||
getForm().clearValidate(name);
|
||||
},
|
||||
resetFields: async () => {
|
||||
await getForm().resetFields();
|
||||
},
|
||||
removeSchemaByFiled: (field: string | string[]) => {
|
||||
getForm().removeSchemaByFiled(field);
|
||||
},
|
||||
getFieldsValue: () => {
|
||||
return getForm().getFieldsValue();
|
||||
},
|
||||
setFieldsValue: <T>(values: T) => {
|
||||
getForm().setFieldsValue<T>(values);
|
||||
},
|
||||
appendSchemaByField: (schema: FormSchema, prefixField?: string | undefined) => {
|
||||
getForm().appendSchemaByField(schema, prefixField);
|
||||
},
|
||||
submit: async (): Promise<any> => {
|
||||
return getForm().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;
|
||||
return [register, methods];
|
||||
}
|
62
src/components/Form/src/hooks/useFormValues.ts
Normal file
62
src/components/Form/src/hooks/useFormValues.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { isArray, isFunction, isObject, isString } from '/@/utils/is';
|
||||
import moment from 'moment';
|
||||
import { unref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { FieldMapToTime } from '../types/form';
|
||||
|
||||
export function useFormValues(
|
||||
transformDateFuncRef: Ref<Fn>,
|
||||
fieldMapToTimeRef: Ref<FieldMapToTime>
|
||||
) {
|
||||
// 处理表单值
|
||||
function handleFormValues(values: any) {
|
||||
if (!isObject(values)) {
|
||||
return {};
|
||||
}
|
||||
const resMap: any = {};
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item;
|
||||
const [key] = item;
|
||||
if ((isArray(value) && value.length === 0) || isFunction(value)) {
|
||||
continue;
|
||||
}
|
||||
const transformDateFunc = unref(transformDateFuncRef);
|
||||
if (isObject(value)) {
|
||||
value = transformDateFunc(value);
|
||||
}
|
||||
if (isArray(value) && value[0]._isAMomentObject && value[1]._isAMomentObject) {
|
||||
value = value.map((item) => transformDateFunc(item));
|
||||
}
|
||||
// 去除空格
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
}
|
||||
resMap[key] = value;
|
||||
}
|
||||
return handleRangeTimeValue(resMap);
|
||||
}
|
||||
/**
|
||||
* @description: 处理时间区间参数
|
||||
*/
|
||||
function handleRangeTimeValue(values: any) {
|
||||
const fieldMapToTime = unref(fieldMapToTimeRef);
|
||||
|
||||
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
|
||||
return values;
|
||||
}
|
||||
|
||||
for (const [field, [startTimeKey, endTimeKey, format = 'YYYY-MM-DD']] of fieldMapToTime) {
|
||||
if (!field || !startTimeKey || !endTimeKey || !values[field]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [startTime, endTime]: string[] = values[field];
|
||||
|
||||
values[startTimeKey] = moment(startTime).format(format);
|
||||
values[endTimeKey] = moment(endTime).format(format);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
return handleFormValues;
|
||||
}
|
43
src/components/Form/src/hooks/useLabelWidth.ts
Normal file
43
src/components/Form/src/hooks/useLabelWidth.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { Ref } from 'vue';
|
||||
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 => {
|
||||
const schemaItem = unref(schemaItemRef);
|
||||
const { labelCol = {}, wrapperCol = {} } = schemaItem.itemProps || {};
|
||||
const { labelWidth, disabledLabelWidth } = schemaItem;
|
||||
|
||||
const { labelWidth: globalLabelWidth } = unref(propsRef) as any;
|
||||
// 如果全局有设置labelWidth, 则所有item使用
|
||||
if ((!globalLabelWidth && !labelWidth) || disabledLabelWidth) {
|
||||
return { labelCol, wrapperCol };
|
||||
}
|
||||
let width = labelWidth || globalLabelWidth;
|
||||
|
||||
if (width) {
|
||||
width = isNumber(width) ? `${width}px` : width;
|
||||
}
|
||||
return {
|
||||
labelCol: { style: { width }, span: 1, ...labelCol },
|
||||
wrapperCol: { style: { width: `calc(100% - ${width})` }, span: 23, ...wrapperCol },
|
||||
};
|
||||
});
|
||||
}
|
107
src/components/Form/src/props.ts
Normal file
107
src/components/Form/src/props.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { FieldMapToTime, FormSchema } from './types/form';
|
||||
import type { PropType } from 'vue';
|
||||
import type { ColEx } from './types';
|
||||
|
||||
export const basicProps = {
|
||||
// 标签宽度 固定宽度
|
||||
labelWidth: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 0,
|
||||
},
|
||||
fieldMapToTime: {
|
||||
type: Array as PropType<FieldMapToTime>,
|
||||
default: () => [],
|
||||
},
|
||||
compact: Boolean as PropType<boolean>,
|
||||
// 表单配置规则
|
||||
schemas: {
|
||||
type: [Array] as PropType<FormSchema[]>,
|
||||
default: () => [],
|
||||
required: true,
|
||||
},
|
||||
mergeDynamicData: {
|
||||
type: Object as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
baseColProps: {
|
||||
type: Object as PropType<any>,
|
||||
},
|
||||
autoSetPlaceHolder: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<'default' | 'small' | 'large'>,
|
||||
default: 'default',
|
||||
},
|
||||
// 禁用表单
|
||||
disabled: Boolean as PropType<boolean>,
|
||||
emptySpan: {
|
||||
type: [Number, Object] as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
// 是否显示收起展开按钮
|
||||
showAdvancedButton: { type: Boolean as PropType<boolean>, default: false },
|
||||
// 转化时间
|
||||
transformDateFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: (date: any) => {
|
||||
return date._isAMomentObject ? date.format('YYYY-MM-DD HH:mm:ss') : date;
|
||||
},
|
||||
},
|
||||
rulesMessageJoinLabel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 超过3行自动折叠
|
||||
autoAdvancedLine: {
|
||||
type: Number as PropType<number>,
|
||||
default: 3,
|
||||
},
|
||||
|
||||
// 是否显示操作按钮
|
||||
showActionButtonGroup: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 操作列Col配置
|
||||
actionColOptions: Object as PropType<ColEx>,
|
||||
// 显示重置按钮
|
||||
showResetButton: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 重置按钮配置
|
||||
resetButtonOptions: Object as PropType<any>,
|
||||
|
||||
// 显示确认按钮
|
||||
showSubmitButton: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Object as PropType<any>,
|
||||
|
||||
// 自定义重置函数
|
||||
resetFunc: Function as PropType<Fn>,
|
||||
submitFunc: Function as PropType<Fn>,
|
||||
|
||||
// 以下为默认props
|
||||
hideRequiredMark: Boolean as PropType<boolean>,
|
||||
|
||||
labelCol: Object as PropType<ColEx>,
|
||||
|
||||
layout: {
|
||||
type: String as PropType<'horizontal' | 'vertical' | 'inline'>,
|
||||
default: 'horizontal',
|
||||
},
|
||||
|
||||
wrapperCol: Object as PropType<any>,
|
||||
|
||||
colon: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
|
||||
labelAlign: String as PropType<string>,
|
||||
};
|
159
src/components/Form/src/types/form.ts
Normal file
159
src/components/Form/src/types/form.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { Form, ValidationRule } from 'ant-design-vue/types/form/form';
|
||||
import type { VNode } from 'vue';
|
||||
import type { BasicButtonProps } from '/@/components/Button/types';
|
||||
import type { FormItem } from './formItem';
|
||||
import type { ColEx, ComponentType } from './index';
|
||||
|
||||
export type FieldMapToTime = [string, [string, string], string?][];
|
||||
|
||||
export interface RenderCallbackParams {
|
||||
schema: FormSchema;
|
||||
values: any;
|
||||
model: any;
|
||||
field: string;
|
||||
}
|
||||
export interface FormActionType extends Form {
|
||||
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;
|
||||
}
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
|
||||
export interface FormProps {
|
||||
// 整个表单所有项宽度
|
||||
labelWidth?: number | string;
|
||||
|
||||
// 整个表单通用Col配置
|
||||
labelCol?: Partial<ColEx>;
|
||||
// 整个表单通用Col配置
|
||||
wrapperCol?: Partial<ColEx>;
|
||||
|
||||
// 通用col配置
|
||||
baseColProps?: any;
|
||||
|
||||
// 表单配置规则
|
||||
schemas?: FormSchema[];
|
||||
// 用于合并到动态控制表单项的 函数values
|
||||
mergeDynamicData?: any;
|
||||
// 紧凑模式,用于搜索表单
|
||||
compact?: boolean;
|
||||
// 空白行span
|
||||
emptySpan?: number | Partial<ColEx>;
|
||||
// 表单内部组件大小
|
||||
size: 'default' | 'small' | 'large';
|
||||
// 是否禁用
|
||||
disabled?: boolean;
|
||||
// 时间区间字段映射成多个
|
||||
fieldMapToTime?: FieldMapToTime;
|
||||
// 自动设置placeholder
|
||||
autoSetPlaceHolder: boolean;
|
||||
// 校验信息是否加入label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
// 是否显示收起展开按钮
|
||||
showAdvancedButton?: boolean;
|
||||
// 超过指定行数自动收起
|
||||
autoAdvancedLine?: number;
|
||||
// 是否显示操作按钮
|
||||
showActionButtonGroup: boolean;
|
||||
|
||||
// 重置按钮配置
|
||||
resetButtonOptions: Partial<BasicButtonProps>;
|
||||
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Partial<BasicButtonProps>;
|
||||
|
||||
// 操作列配置
|
||||
actionColOptions: Partial<ColEx>;
|
||||
|
||||
// 显示重置按钮
|
||||
showResetButton: boolean;
|
||||
// 显示确认按钮
|
||||
showSubmitButton: boolean;
|
||||
|
||||
resetFunc: () => Promise<void>;
|
||||
submitFunc: () => Promise<void>;
|
||||
transformDateFunc: (date: any) => string;
|
||||
colon?: boolean;
|
||||
}
|
||||
export interface FormSchema {
|
||||
// 字段名
|
||||
field: string;
|
||||
changeEvent?: string;
|
||||
// 标签名
|
||||
label: string;
|
||||
// 文本右侧帮助文本
|
||||
helpMessage?: string | string[];
|
||||
// BaseHelp组件props
|
||||
helpComponentProps?: Partial<HelpComponentProps>;
|
||||
// label宽度,有传的话 itemProps配置的 labelCol 和WrapperCol会失效
|
||||
labelWidth?: string | number;
|
||||
// 禁用调有formModel全局设置的labelWidth,自己手动设置 labelCol和wrapperCol
|
||||
disabledLabelWidth?: boolean;
|
||||
// 组件
|
||||
component: ComponentType;
|
||||
// 组件参数
|
||||
componentProps?: any;
|
||||
|
||||
// 校验规则
|
||||
rules?: ValidationRule[];
|
||||
// 校验信息是否加入label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// 参考formModelItem
|
||||
itemProps?: Partial<FormItem>;
|
||||
|
||||
// formModelItem外层的col配置
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any;
|
||||
isAdvanced?: boolean;
|
||||
|
||||
// 配合详情组件
|
||||
span?: number;
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
// 渲染form-item标签内的内容
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
// 渲染 col内容,需要外层包裹 form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[];
|
||||
|
||||
renderComponentContent?: (renderCallbackParams: RenderCallbackParams) => any;
|
||||
|
||||
// 自定义slot, 在 from-item内
|
||||
slot?: string;
|
||||
|
||||
// 自定义slot,类似renderColContent
|
||||
colSlot?: string;
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
dynamicRules?: (renderCallbackParams: RenderCallbackParams) => ValidationRule[];
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string;
|
||||
// 是否显示序号
|
||||
showIndex: boolean;
|
||||
// 文本列表
|
||||
text: any;
|
||||
// 颜色
|
||||
color: string;
|
||||
// 字体大小
|
||||
fontSize: string;
|
||||
icon: string;
|
||||
absolute: boolean;
|
||||
// 定位
|
||||
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/types/form/form-item';
|
||||
import type { Col } from 'ant-design-vue/types/grid/col';
|
||||
import type { 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?: Col;
|
||||
|
||||
/**
|
||||
* 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?: Col;
|
||||
/**
|
||||
* Set sub label htmlFor.
|
||||
*/
|
||||
htmlFor?: string;
|
||||
/**
|
||||
* text align of label
|
||||
*/
|
||||
labelAlign?: 'left' | 'right';
|
||||
/**
|
||||
* a key of model. In the use 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 use 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;
|
||||
}
|
113
src/components/Form/src/types/index.ts
Normal file
113
src/components/Form/src/types/index.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ColSpanType } from 'ant-design-vue/types/grid/col';
|
||||
|
||||
export interface ColEx {
|
||||
style: object;
|
||||
/**
|
||||
* 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 =
|
||||
| 'Input'
|
||||
| 'InputGroup'
|
||||
| 'InputPassword'
|
||||
| 'InputSearch'
|
||||
| 'InputTextArea'
|
||||
| 'InputNumber'
|
||||
| 'InputCountDown'
|
||||
| 'Select'
|
||||
| 'DictSelect'
|
||||
| 'SelectOptGroup'
|
||||
| 'SelectOption'
|
||||
| 'TreeSelect'
|
||||
| 'Transfer'
|
||||
| 'Radio'
|
||||
| 'RadioButton'
|
||||
| 'RadioGroup'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'AutoComplete'
|
||||
| 'Cascader'
|
||||
| 'DatePicker'
|
||||
| 'MonthPicker'
|
||||
| 'RangePicker'
|
||||
| 'WeekPicker'
|
||||
| 'TimePicker'
|
||||
| 'ImageUpload'
|
||||
| 'Switch'
|
||||
| 'StrengthMeter'
|
||||
| 'Render';
|
Reference in New Issue
Block a user