<template>
<el-form
v-bind="props"
v-loading="formLoading"
element-loading-text="加载中..."
element-loading-background="rgba(255, 255, 255, 0.5)"
ref="formRef"
:model="getModelValue"
:rules="rules"
inline
scroll-to-error
>
<slot name="header"></slot>
<el-form-item v-for="item in formItems" v-bind="item" :key="item.field" :prop="item.field" :label="item.label" for=" " :class="getFormItemClass(item)">
<div class="el-form-item__content__warp" :style="{width: item.width || formItemWidth}">
<el-select
v-if="item.compName === 'ElSelect'"
v-bind="item.props"
v-model="getModelValue[item.field]"
filterable
fit-input-width
:clearable="!!(item.props?.clearable !== false)"
style="width: 100%"
>
<el-option
v-for="(oItem, index) in getPropsKeyValue(item, 'options')"
:key="index"
:label="oItem[getPropsKeyValue(item)]"
:value="oItem[getPropsKeyValue(item, 'value')]"
>
<BaseToolTip :rows="1" :content="oItem[getPropsKeyValue(item)]">
{{ oItem[getPropsKeyValue(item)] }}
</BaseToolTip>
</el-option>
</el-select>
<component
v-bind="item.props"
:is="item.compName"
v-else-if="item.compName"
v-model="getModelValue[item.field]"
:clearable="item.props?.clearable !== false"
></component>
<template v-else>
<el-input
v-show="!$slots?.[item.field]"
v-model="getModelValue[item.field]"
v-bind="item.props"
:maxlength="item.props?.maxlength || 21"
:clearable="item.props?.clearable !== false"
:placeholder="item.props?.placeholder || '请输入'"
style="width: 100%"
></el-input>
<slot :name="item.field" :item="item"></slot>
</template>
</div>
</el-form-item>
<el-form-item v-if="isBtns" class="el-form-item--btns" label-position="">
<slot name="btns">
<el-button v-baseDebounceClick type="primary" :disabled="getDisabled('submit')" @click="search"> 查 询 </el-button>
<el-button v-baseDebounceClick plain type="primary" :disabled="getDisabled('reset')" @click="reset">
重 置
</el-button>
</slot>
</el-form-item>
<slot></slot>
</el-form>
</template>
<script setup>
import {ref, toRefs, computed, watch} from 'vue';
import {ElForm} from 'element-plus';
const props = defineProps({
...ElForm.props,
modelValue: {
type: Object,
default: () => ({})
},
formItems: {
type: Array,
required: true,
validator(value) {
return value.every(item => {
return ['field', 'label'].every(key => Object.keys(item).includes(key));
});
}
},
formItemWidth: {
type: String,
default: '250px'
},
isBtns: {
type: Boolean,
default: true
}
});
const emits = defineEmits([...Object.keys(ElForm.emits), 'search', 'query', 'reset', 'update:modelValue', 'change']);
const formLoading = ref(false);
const {formItems, modelValue} = toRefs(props);
const formRef = ref(null);
const modelDefaultValue = ref({});
const rules = computed(() => {
return formItems.value.reduce((prev, current) => {
if (current?.field && current?.rules) {
const newCurrent = prev;
newCurrent[current.field] = current.rules;
return newCurrent;
}
return prev;
}, {});
});
const getDefaultValue = computed(() => {
return formItems.value.map(({defaultValue}) => defaultValue);
});
const getRequiredAllValue = computed(() => {
return formItems.value
.filter(({required}) => required)
.map(({field}) => field)
.every(key => getModelValue.value[key] || [0, false].includes(getModelValue.value[key]));
});
const getModelValue = computed({
get() {
return new Proxy(modelValue.value, {
get: (target, field) => Reflect.get(target, field),
set: (target, field, value) => {
updateFieldValue(field, value);
return true;
}
});
}
});
const updateFieldEvent = (eventType, key, value) => {
const fIndex = formItems.value.findIndex(({field}) => field === key);
if (formItems.value?.[fIndex]?.props?.[eventType]) {
formItems.value[fIndex]['props'][eventType](value, modelValue.value);
}
};
const updateFieldValue = (field, value) => {
const target = modelValue.value;
const oldValue = target[field];
let flag = false;
if (Array.isArray(value) && Array.isArray(oldValue)) {
flag = isOldNewArrayChange(oldValue, value);
} else if (Array.isArray(value) || Array.isArray(oldValue)) {
flag = true;
} else {
flag = oldValue !== value;
}
if (flag) {
emits('change', {field, value});
target[field] = value;
emits('update:modelValue', {...target});
}
updateFieldEvent('onUpdate', field, value);
};
const getPropsKeyValue = (item, key = 'label') => {
return item?.props?.[key] || key;
};
const isOldNewArrayChange = (oldArr, newArr) => {
return oldArr.length === newArr.length ? oldArr.some(item => !newArr.includes(item)) : true;
};
const initModelValue = (status = false) => {
formItems.value
.filter(item => 'defaultValue' in item)
.map(item => {
const oldValue = getModelValue.value[item.field];
let flag = status;
if (!(item.field in getModelValue.value) || Array.isArray(oldValue) || Array.isArray(item.defaultValue)) {
flag = true;
} else if (Array.isArray(oldValue) && Array.isArray(item.defaultValue)) {
flag = isOldNewArrayChange(oldValue, item.defaultValue);
} else {
flag = status;
}
if (flag) {
getModelValue.value[item.field] = item.defaultValue;
}
});
};
const getFormItemClass = item => {
let str = item?.class ?? '';
if (item?.props?.hidden) {
str += 'is-hidden';
}
return str;
};
const doEmits = list => {
list.map(key => {
emits(key, JSON.parse(JSON.stringify(getModelValue.value)));
});
};
const search = () => {
if (!getDisabled('submit')) {
formRef.value.validate(valid => {
if (valid) {
doEmits(['search', 'query']);
}
});
}
};
const reset = () => {
initModelValue(true);
formRef.value.resetFields();
doEmits(['reset', 'query']);
};
const getDisabled = type => !getRequiredAllValue.value;
watch(
() => getDefaultValue.value,
val => {
initModelValue();
},
{immediate: true, deep: true}
);
defineExpose({
formRef,
formLoading
});
</script>
<style lang="scss">
.el-form--inline.el-form--label-top .el-form-item {
&.el-form-item--btns {
display: flex;
align-items: flex-end;
}
&.is-hidden {
display: none !important;
opacity: 0;
pointer-events: none;
}
}
</style>
console