签入版本
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export { default as BasicForm } from './src/BasicForm.vue';
|
||||
export { useForm } from './src/hooks/useForm';
|
||||
export * from './src/types/form';
|
||||
export * from './src/types/index';
|
||||
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<el-form v-bind="getBindValue" :model="formModel" ref="formElRef">
|
||||
<el-row v-bind="getRow">
|
||||
<el-col
|
||||
v-bind="getCol"
|
||||
v-for="schema in getSchema"
|
||||
:key="schema.field"
|
||||
:span="getHidden(schema) ? 0 : getCol.span"
|
||||
>
|
||||
<el-form-item
|
||||
v-bind="schema"
|
||||
:label="schema.label"
|
||||
:prop="schema.field"
|
||||
:showFeedback="schema.showFeedback"
|
||||
v-if="!getHidden(schema)"
|
||||
>
|
||||
<!--标签名右侧温馨提示-->
|
||||
<template #label v-if="schema.labelMessage">
|
||||
<div class="flex items-center">
|
||||
{{ schema.label }}
|
||||
<el-tooltip
|
||||
trigger="hover"
|
||||
:style="schema.labelMessageStyle"
|
||||
:content="schema.labelMessage"
|
||||
>
|
||||
<el-icon size="18" class="ml-1 text-gray-400 cursor-pointer el-input__icon">
|
||||
<QuestionCircleOutlined />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--判断插槽-->
|
||||
<template v-if="schema.slot">
|
||||
<slot
|
||||
:name="schema.slot"
|
||||
:model="formModel"
|
||||
:field="schema.field"
|
||||
:value="formModel[schema.field]"
|
||||
></slot>
|
||||
</template>
|
||||
|
||||
<!--NCheckbox-->
|
||||
<template v-else-if="schema.component === 'Checkbox'">
|
||||
<el-checkbox-group
|
||||
v-bind="getSpecComponentProps(schema)"
|
||||
v-model="formModel[schema.field]"
|
||||
:class="{ isFull: schema.isFull != false && getProps.isFull }"
|
||||
>
|
||||
<el-space>
|
||||
<el-checkbox
|
||||
v-for="item in schema.componentProps?.options"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-space>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
|
||||
<!--elRadioGroup-->
|
||||
<template v-else-if="schema.component === 'RadioGroup'">
|
||||
<el-radio-group
|
||||
v-bind="getSpecComponentProps(schema)"
|
||||
v-model="formModel[schema.field]"
|
||||
:class="{ isFull: schema.isFull != false && getProps.isFull }"
|
||||
>
|
||||
<el-space>
|
||||
<el-radio
|
||||
v-for="item in schema.componentProps?.options"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-space>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
|
||||
<!--elSelect-->
|
||||
<template v-else-if="schema.component === 'Select'">
|
||||
<el-select
|
||||
v-bind="getSpecComponentProps(schema)"
|
||||
v-model="formModel[schema.field]"
|
||||
:class="{ isFull: schema.isFull != false && getProps.isFull }"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in schema.componentProps?.options"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<!-- 时间段选择器 -->
|
||||
<template v-else-if="schema.component === 'Daterange'">
|
||||
<el-date-picker
|
||||
v-bind="getSpecComponentProps(schema)"
|
||||
value-format="YYYY-MM-DD"
|
||||
v-model="formModel[schema.field]"
|
||||
style="width: 220px"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!--BasicSelect-->
|
||||
<template v-else-if="schema.component === 'BasicSelect'">
|
||||
<BasicSelect v-bind="getSpecComponentProps(schema)" v-model="formModel[schema.field]" />
|
||||
</template>
|
||||
|
||||
<!--动态渲染表单组件-->
|
||||
<component
|
||||
v-else
|
||||
v-bind="getComponentProps(schema)"
|
||||
:is="`el-${schema.component}`"
|
||||
v-model="formModel[schema.field]"
|
||||
:type="schema.type"
|
||||
/>
|
||||
<!--组件后面的内容-->
|
||||
<template v-if="schema.suffix">
|
||||
<slot
|
||||
:name="schema.suffix"
|
||||
:model="formModel"
|
||||
:field="schema.field"
|
||||
:value="formModel[schema.field]"
|
||||
></slot>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!--提交 重置 展开 收起 按钮-->
|
||||
<el-col :span="6" v-if="getProps.showActionButtonGroup">
|
||||
<el-form-item :label-width="isHorizontal ? '20px' : getProps.labelWidth">
|
||||
<el-space>
|
||||
<el-button
|
||||
v-if="getProps.showSubmitButton"
|
||||
v-bind="getSubmitBtnOptions"
|
||||
@click="handleSubmit"
|
||||
icon="Search"
|
||||
:loading="loadingSub"
|
||||
>{{ getProps.submitButtonText }}</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="getProps.showResetButton"
|
||||
v-bind="getResetBtnOptions"
|
||||
@click="resetFields"
|
||||
>{{ getProps.resetButtonText }}</el-button
|
||||
>
|
||||
<!-- <el-button
|
||||
type="primary"
|
||||
text
|
||||
icon-placement="right"
|
||||
v-if="isHorizontal && getProps.showAdvancedButton"
|
||||
@click="unfoldToggle"
|
||||
>
|
||||
<template #icon>
|
||||
<el-icon size="14" class="el-input__icon unfold-icon" v-if="overflow">
|
||||
<DownOutlined />
|
||||
</el-icon>
|
||||
<el-icon size="14" class="el-input__icon unfold-icon" v-else>
|
||||
<UpOutlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
{{ overflow ? '展开' : '收起' }}
|
||||
</el-button> -->
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, useAttrs, computed, unref, onMounted, watch } from 'vue';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { useFormEvents } from './hooks/useFormEvents';
|
||||
import { useFormValues } from './hooks/useFormValues';
|
||||
import { BasicSelect } from '@/components/Select';
|
||||
|
||||
import { basicProps } from './props';
|
||||
import { QuestionCircleOutlined } from '@vicons/antd';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
import type { FormSchema, FormProps, FormActionType } from './types/form';
|
||||
|
||||
import { isArray, isBoolean, isFunction } from '@/utils/is/index';
|
||||
import { deepMerge } from '@/utils';
|
||||
|
||||
const props = defineProps({ ...basicProps });
|
||||
const emit = defineEmits(['reset', 'submit', 'register', 'advanced']);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const defaultFormModel = ref<Recordable>({});
|
||||
const formModel = reactive<Recordable>({});
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||
const formElRef = ref<Nullable<FormActionType>>(null);
|
||||
// const gridCollapsed = ref(!props.collapsed);
|
||||
const loadingSub = ref(false);
|
||||
const isUpdateDefaultRef = ref(false);
|
||||
|
||||
const getSubmitBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
size: props.size,
|
||||
type: 'primary',
|
||||
},
|
||||
props.submitButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
const getResetBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
size: props.size,
|
||||
type: 'default',
|
||||
},
|
||||
props.resetButtonOptions,
|
||||
);
|
||||
});
|
||||
|
||||
function getComponentProps(schema) {
|
||||
const compProps = schema.componentProps ?? {};
|
||||
const component = schema.component;
|
||||
return {
|
||||
class: schema.isFull != false && getProps.value.isFull ? 'w-full' : '',
|
||||
clearable: true,
|
||||
placeholder: createPlaceholderMessage(unref(component)),
|
||||
...compProps,
|
||||
};
|
||||
}
|
||||
|
||||
function getHidden(schema): boolean {
|
||||
const hidden = schema.hidden;
|
||||
const field = schema.field;
|
||||
if (isBoolean(hidden)) return hidden;
|
||||
|
||||
if (isFunction(hidden)) {
|
||||
const values = getFieldsValue();
|
||||
const status = hidden({ schema, values, model: formModel, field });
|
||||
return status;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getSpecComponentProps(schema) {
|
||||
const compProps = schema.componentProps ?? {};
|
||||
return {
|
||||
...compProps,
|
||||
};
|
||||
}
|
||||
|
||||
const getProps = computed((): FormProps => {
|
||||
const formProps = { ...props, ...unref(propsRef) } as FormProps;
|
||||
const rulesObj: any = {
|
||||
rules: {},
|
||||
};
|
||||
const schemas: any = formProps.schemas || [];
|
||||
schemas.forEach((item) => {
|
||||
if (item.rules && isArray(item.rules)) {
|
||||
rulesObj.rules[item.field] = item.rules;
|
||||
}
|
||||
});
|
||||
return { ...formProps, ...unref(rulesObj) };
|
||||
});
|
||||
|
||||
const isHorizontal = computed(() => {
|
||||
const { layout } = unref(getProps);
|
||||
return layout === 'horizontal';
|
||||
});
|
||||
|
||||
const getRow = computed(() => {
|
||||
const { rowProps } = unref(getProps);
|
||||
return {
|
||||
...rowProps,
|
||||
// collapsed: isHorizontal .value ? gridCollapsed.value : false,
|
||||
// responsive: 'screen',
|
||||
};
|
||||
});
|
||||
|
||||
const getCol = computed(() => {
|
||||
const { colProps } = unref(getProps);
|
||||
return {
|
||||
...colProps,
|
||||
// collapsed: isHorizontal .value ? gridCollapsed.value : false,
|
||||
// responsive: 'screen',
|
||||
};
|
||||
});
|
||||
|
||||
const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable));
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||
for (const schema of schemas) {
|
||||
const { defaultValue } = schema;
|
||||
// handle date type
|
||||
// dateItemType.includes(component as string)
|
||||
if (defaultValue) {
|
||||
schema.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
return schemas as FormSchema[];
|
||||
});
|
||||
|
||||
const { handleFormValues, initDefault } = useFormValues({
|
||||
defaultFormModel,
|
||||
getSchema,
|
||||
formModel,
|
||||
});
|
||||
|
||||
const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue } =
|
||||
useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
formElRef: formElRef as Ref<FormActionType>,
|
||||
defaultFormModel,
|
||||
loadingSub,
|
||||
handleFormValues,
|
||||
});
|
||||
|
||||
// function unfoldToggle() {
|
||||
// gridCollapsed.value = !gridCollapsed.value;
|
||||
// emit('advanced', gridCollapsed.value);
|
||||
// }
|
||||
|
||||
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||
}
|
||||
|
||||
async function setSchema(schemaProps: FormSchema[]): Promise<void> {
|
||||
schemaRef.value = schemaProps;
|
||||
}
|
||||
|
||||
//更新loading状态
|
||||
function setLoadingSub(status: boolean): void {
|
||||
loadingSub.value = status;
|
||||
}
|
||||
|
||||
const formActionType: Partial<FormActionType> = {
|
||||
getFieldsValue,
|
||||
setFieldsValue,
|
||||
resetFields,
|
||||
validate,
|
||||
clearValidate,
|
||||
setProps,
|
||||
setSchema,
|
||||
setLoadingSub,
|
||||
submit: handleSubmit,
|
||||
};
|
||||
|
||||
watch(
|
||||
() => getSchema.value,
|
||||
(schema) => {
|
||||
if (unref(isUpdateDefaultRef)) {
|
||||
return;
|
||||
}
|
||||
if (schema?.length) {
|
||||
initDefault();
|
||||
isUpdateDefaultRef.value = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
initDefault();
|
||||
emit('register', formActionType);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
...formActionType,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.isFull {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.unfold-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin-left: -3px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ComponentType } from './types/index';
|
||||
|
||||
/**
|
||||
* @description: 生成 placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component === 'Input') return '请输入';
|
||||
if (
|
||||
['Picker', 'Select', 'Checkbox', 'Radio', 'Switch', 'DatePicker', 'TimePicker'].includes(
|
||||
component,
|
||||
)
|
||||
)
|
||||
return '请选择';
|
||||
return '';
|
||||
}
|
||||
|
||||
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
|
||||
|
||||
function genType() {
|
||||
return [...DATE_TYPE, 'RangePicker'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段
|
||||
*/
|
||||
export const dateItemType = genType();
|
||||
|
||||
export function defaultType(component) {
|
||||
if (component === 'Input') return '';
|
||||
if (component === 'InputNumber') return null;
|
||||
return ['Picker', 'Select', 'Checkbox', 'Radio', 'Switch', 'DatePicker', 'TimePicker'].includes(
|
||||
component,
|
||||
)
|
||||
? ''
|
||||
: undefined;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { FormProps, FormActionType, UseFormReturnType } from '../types/form';
|
||||
|
||||
import type { DynamicProps } from '/#/utils';
|
||||
|
||||
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
||||
import { isProdMode } from '@/utils/env';
|
||||
import { getDynamicProps } from '@/utils';
|
||||
|
||||
type Props = Partial<DynamicProps<FormProps>>;
|
||||
|
||||
export function useForm(props?: Props): UseFormReturnType {
|
||||
const formRef = ref<Nullable<FormActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
|
||||
async function getForm() {
|
||||
const form = unref(formRef);
|
||||
if (!form) {
|
||||
console.error(
|
||||
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!',
|
||||
);
|
||||
}
|
||||
await nextTick();
|
||||
return form as FormActionType;
|
||||
}
|
||||
|
||||
function register(instance: FormActionType) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
formRef.value = null;
|
||||
loadedRef.value = null;
|
||||
});
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
|
||||
|
||||
formRef.value = instance;
|
||||
loadedRef.value = true;
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
() => {
|
||||
props && instance.setProps(getDynamicProps(props));
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const methods: FormActionType = {
|
||||
setProps: async (formProps: Partial<FormProps>) => {
|
||||
const form = await getForm();
|
||||
await form.setProps(formProps);
|
||||
},
|
||||
|
||||
resetFields: async () => {
|
||||
getForm().then(async (form) => {
|
||||
await form.resetFields();
|
||||
});
|
||||
},
|
||||
|
||||
clearValidate: async (name?: string | string[]) => {
|
||||
const form = await getForm();
|
||||
await form.clearValidate(name);
|
||||
},
|
||||
|
||||
getFieldsValue: <T>() => {
|
||||
return unref(formRef)?.getFieldsValue() as T;
|
||||
},
|
||||
|
||||
setFieldsValue: async <T>(values: T) => {
|
||||
const form = await getForm();
|
||||
await form.setFieldsValue(values as Recordable<any>);
|
||||
},
|
||||
|
||||
submit: async (): Promise<any> => {
|
||||
const form = await getForm();
|
||||
return form.submit();
|
||||
},
|
||||
|
||||
validate: async (nameList?: any[]): Promise<Recordable> => {
|
||||
const form = await getForm();
|
||||
return form.validate(nameList);
|
||||
},
|
||||
|
||||
setLoadingSub: (value: boolean) => {
|
||||
loadedRef.value = value;
|
||||
},
|
||||
|
||||
setSchema: async (values) => {
|
||||
const form = await getForm();
|
||||
form.setSchema(values);
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { provide, inject } from 'vue';
|
||||
|
||||
const key = Symbol('formElRef');
|
||||
|
||||
export function createFormContext(instance) {
|
||||
provide(key, instance);
|
||||
}
|
||||
|
||||
export function useFormContext() {
|
||||
return inject(key);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { FormProps, FormSchema, FormActionType } from '../types/form';
|
||||
import { unref, toRaw } from 'vue';
|
||||
import { isDef, isFunction } from '@/utils/is';
|
||||
|
||||
declare type EmitType = (event: string, ...args: any[]) => void;
|
||||
|
||||
interface UseFormActionContext {
|
||||
emit: EmitType;
|
||||
getProps: ComputedRef<FormProps>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
formElRef: Ref<FormActionType>;
|
||||
defaultFormModel: Recordable;
|
||||
loadingSub: Ref<boolean>;
|
||||
handleFormValues: Function;
|
||||
}
|
||||
|
||||
export function useFormEvents({
|
||||
emit,
|
||||
getProps,
|
||||
formModel,
|
||||
getSchema,
|
||||
formElRef,
|
||||
defaultFormModel,
|
||||
loadingSub,
|
||||
handleFormValues,
|
||||
}: UseFormActionContext) {
|
||||
// 验证
|
||||
async function validate() {
|
||||
return unref(formElRef)?.validate();
|
||||
}
|
||||
|
||||
// 提交
|
||||
async function handleSubmit(e?: Event): Promise<object | boolean> {
|
||||
e && e.preventDefault();
|
||||
loadingSub.value = true;
|
||||
const { submitFunc } = unref(getProps);
|
||||
if (submitFunc && isFunction(submitFunc)) {
|
||||
await submitFunc();
|
||||
loadingSub.value = false;
|
||||
return false;
|
||||
}
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return false;
|
||||
try {
|
||||
await validate();
|
||||
const values = getFieldsValue();
|
||||
loadingSub.value = false;
|
||||
emit('submit', values);
|
||||
return values;
|
||||
} catch (error: any) {
|
||||
emit('submit', false);
|
||||
loadingSub.value = false;
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//清空校验
|
||||
async function clearValidate() {
|
||||
await unref(formElRef)?.resetFields();
|
||||
}
|
||||
|
||||
//重置
|
||||
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[key] = unref(defaultFormModel)[key] || null;
|
||||
});
|
||||
await clearValidate();
|
||||
const fromValues = handleFormValues(toRaw(unref(formModel)));
|
||||
emit('reset', fromValues);
|
||||
submitOnReset && (await handleSubmit());
|
||||
}
|
||||
|
||||
//获取表单值
|
||||
function getFieldsValue(): Recordable {
|
||||
const formEl = unref(formElRef);
|
||||
if (!formEl) return {};
|
||||
return handleFormValues(toRaw(unref(formModel)));
|
||||
}
|
||||
|
||||
//设置表单字段值
|
||||
function setFieldsValue(values: Recordable): void {
|
||||
const fields = unref(getSchema)
|
||||
.map((item) => item.field)
|
||||
.filter(Boolean);
|
||||
|
||||
// 兼容 a.b.c 写法
|
||||
const decimal = '.';
|
||||
const decimalKeyList = fields.filter((item) => item.indexOf(decimal) >= 0);
|
||||
|
||||
Object.keys(values).forEach((key) => {
|
||||
const value = values[key];
|
||||
const isKey = Reflect.has(values, key);
|
||||
if (isKey && fields.includes(key)) {
|
||||
formModel[key] = value;
|
||||
} else {
|
||||
decimalKeyList.forEach((itemKey: string) => {
|
||||
try {
|
||||
const value = eval('values' + decimal + itemKey);
|
||||
if (isDef(value)) {
|
||||
formModel[itemKey] = value;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setLoadingSub(value: boolean): void {
|
||||
loadingSub.value = value;
|
||||
}
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
validate,
|
||||
resetFields,
|
||||
getFieldsValue,
|
||||
clearValidate,
|
||||
setFieldsValue,
|
||||
setLoadingSub,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '@/utils/is';
|
||||
import { unref } from 'vue';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import type { FormSchema } from '../types/form';
|
||||
import { set } from 'lodash-es';
|
||||
|
||||
interface UseFormValuesContext {
|
||||
defaultFormModel: Ref<any>;
|
||||
getSchema: ComputedRef<FormSchema[]>;
|
||||
formModel: Recordable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desription 变异目标对象
|
||||
*/
|
||||
function strShiftObject(key: string, value: any, target: Recordable) {
|
||||
const pattern = /^\{(.+)\}$/;
|
||||
if (pattern.test(key)) {
|
||||
const match = key.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const keys = match[1].split(',');
|
||||
value = isObject(value) ? value : {};
|
||||
keys.forEach((k) => {
|
||||
set(target, k.trim(), value[k.trim()]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useFormValues({ defaultFormModel, getSchema, formModel }: UseFormValuesContext) {
|
||||
// 加工 form values
|
||||
function handleFormValues(values: Recordable) {
|
||||
if (!isObject(values)) {
|
||||
return {};
|
||||
}
|
||||
if (values) {
|
||||
for (let key in values) {
|
||||
if (values[key]) {
|
||||
if (values[key] instanceof Array) {
|
||||
values[key] = values[key].join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const res: Recordable = {};
|
||||
for (const item of Object.entries(values)) {
|
||||
let [, value] = item;
|
||||
const [key] = item;
|
||||
if (
|
||||
!key ||
|
||||
(isArray(value) && value.length === 0) ||
|
||||
isFunction(value) ||
|
||||
isNullOrUnDef(value)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// 删除空格
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
}
|
||||
if (!strShiftObject(key, value, res)) {
|
||||
set(res, key, value);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//初始化默认值
|
||||
function initDefault() {
|
||||
const schemas = unref(getSchema);
|
||||
const obj: Recordable = {};
|
||||
schemas.forEach((item) => {
|
||||
const { defaultValue } = item;
|
||||
if (!isNullOrUnDef(defaultValue)) {
|
||||
obj[item.field] = defaultValue;
|
||||
formModel[item.field] = defaultValue;
|
||||
}
|
||||
});
|
||||
defaultFormModel.value = obj;
|
||||
}
|
||||
|
||||
return { handleFormValues, initDefault };
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import { FormSchema } from './types/form';
|
||||
import type { ColProps } from 'element-plus/es/components/col/src/col';
|
||||
import type { ButtonProps } from 'element-plus/es/components/button/src/button';
|
||||
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { FormRules } from 'element-plus';
|
||||
export const basicProps = {
|
||||
//表单数据对象
|
||||
model: {
|
||||
type: Object,
|
||||
},
|
||||
//表单验证规则
|
||||
rules: {
|
||||
type: Object as PropType<FormRules>,
|
||||
},
|
||||
//是否展示为行内表单
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//表单域标签的位置
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
},
|
||||
// 标签宽度 固定宽度
|
||||
labelWidth: {
|
||||
type: [Number, String] as PropType<number | string>,
|
||||
default: 80,
|
||||
},
|
||||
//表单域标签的后缀
|
||||
labelSuffix: {
|
||||
type: String,
|
||||
},
|
||||
//是否显示必填字段的标签旁边的红色星号
|
||||
hideRequiredAsterisk: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否显示校验错误信息
|
||||
showMessage: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
//是否以行内形式展示校验信息
|
||||
inlineMessage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否在输入框中显示校验结果反馈图标
|
||||
statusIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
//是否在 rules 属性改变后立即触发一次验证
|
||||
validateOnRuleChange: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
//大小
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
//是否禁用该表单内的所有组件。 若设置为 true,则表单内组件上的 disabled 属性不再生效
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
/* =============================== 自定义开始 ================================= */
|
||||
|
||||
// 表单配置规则
|
||||
schemas: {
|
||||
type: [Array] as PropType<FormSchema[]>,
|
||||
default: () => [],
|
||||
},
|
||||
//布局方式
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'vertical',
|
||||
},
|
||||
//组件是否width 100%
|
||||
isFull: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
//是否显示操作按钮(查询/重置)
|
||||
showActionButtonGroup: propTypes.bool.def(true),
|
||||
// 显示重置按钮
|
||||
showResetButton: propTypes.bool.def(true),
|
||||
//重置按钮配置
|
||||
resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
// 显示确认按钮
|
||||
showSubmitButton: propTypes.bool.def(true),
|
||||
// 确认按钮配置
|
||||
submitButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||
//展开收起按钮
|
||||
showAdvancedButton: propTypes.bool.def(true),
|
||||
// 确认按钮文字
|
||||
submitButtonText: {
|
||||
type: String,
|
||||
default: '查询',
|
||||
},
|
||||
//重置按钮文字
|
||||
resetButtonText: {
|
||||
type: String,
|
||||
default: '重置',
|
||||
},
|
||||
//row 配置
|
||||
rowProps: Object as PropType<ColProps>,
|
||||
//col配置
|
||||
colProps: Object as PropType<ColProps>,
|
||||
//grid 样式
|
||||
baseGridStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
},
|
||||
//是否折叠
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
//默认展示的行数
|
||||
collapsedRows: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ComponentType } from './index';
|
||||
import type { CSSProperties } from 'vue';
|
||||
// import type { ElCol, ElButton } from 'element-plus';
|
||||
import type { ColProps } from 'element-plus/es/components/col/src/col';
|
||||
import type { ButtonProps } from 'element-plus/es/components/button/src/button';
|
||||
|
||||
export interface RenderReturnParams {
|
||||
schema: FormSchema;
|
||||
values: Recordable;
|
||||
model: Recordable;
|
||||
field: string;
|
||||
}
|
||||
|
||||
export interface componentProps {
|
||||
options?: any[];
|
||||
placeholder?: string;
|
||||
showButton?: boolean;
|
||||
onInput?: () => Recordable;
|
||||
onChange?: () => Recordable;
|
||||
}
|
||||
|
||||
export interface FormSchema {
|
||||
field: string;
|
||||
label?: string;
|
||||
type?:string;
|
||||
labelMessage?: string;
|
||||
labelMessageStyle?: CSSProperties;
|
||||
defaultValue?: any;
|
||||
component?: ComponentType;
|
||||
componentProps?: any;
|
||||
slot?: string;
|
||||
rules?: object | object[];
|
||||
rowProps?: ColProps;
|
||||
colProps?: ColProps;
|
||||
isFull?: boolean;
|
||||
suffix?: string;
|
||||
showFeedback?: boolean;
|
||||
showLabel?: boolean;
|
||||
requireMarkPlacement?: string;
|
||||
hidden?: boolean | ((renderCallbackParams: RenderReturnParams) => boolean);
|
||||
}
|
||||
|
||||
export interface FormProps {
|
||||
model?: Recordable;
|
||||
labelWidth?: number | string;
|
||||
schemas?: FormSchema[];
|
||||
inline: boolean;
|
||||
layout?: string;
|
||||
size: string;
|
||||
labelPlacement: string;
|
||||
isFull: boolean;
|
||||
showActionButtonGroup?: boolean;
|
||||
showResetButton?: boolean;
|
||||
resetButtonOptions?: Partial<ButtonProps>;
|
||||
showSubmitButton?: boolean;
|
||||
showAdvancedButton?: boolean;
|
||||
submitButtonOptions?: Partial<ButtonProps>;
|
||||
submitButtonText?: string;
|
||||
resetButtonText?: string;
|
||||
rowProps?: ColProps;
|
||||
colProps?: ColProps;
|
||||
resetFunc?: () => Promise<void>;
|
||||
submitFunc?: () => Promise<void>;
|
||||
submitOnReset?: boolean;
|
||||
baseGridStyle?: CSSProperties;
|
||||
draggable?: boolean;
|
||||
collapsed?: boolean;
|
||||
collapsedRows?: number;
|
||||
}
|
||||
|
||||
export interface FormActionType {
|
||||
submit: () => Promise<any>;
|
||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||
setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
|
||||
setFieldsValue: (values: Recordable) => void;
|
||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||
getFieldsValue: () => Recordable;
|
||||
resetFields: () => Promise<void>;
|
||||
validate: (nameList?: any[]) => Promise<any>;
|
||||
setLoadingSub: (status: boolean) => void;
|
||||
}
|
||||
|
||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||
|
||||
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
@@ -0,0 +1,18 @@
|
||||
export type ComponentType =
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'Select'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'Checkbox'
|
||||
| 'ColorPicker'
|
||||
| 'AutoComplete'
|
||||
| 'Cascader'
|
||||
| 'DatePicker'
|
||||
| 'TimePicker'
|
||||
| 'TimeSelect'
|
||||
| 'Switch'
|
||||
| 'Upload'
|
||||
| 'Slider'
|
||||
| 'Rate'
|
||||
| 'BasicSelect';
|
||||
Reference in New Issue
Block a user