在项目开发中碰到下面的场景
在el-form-item的label右侧有一个图标,鼠标移入有提示效果,考虑到表单的每一项都是相同结构且表单字段较多,于是决定对el-form-item二次封装,二次封装需要在满足自定义需求的同时保证el-form-item原有的功能正常使用,最关键的是能与el-form中的配置联动,如在el-form中配置的rules,label-wodth属性。
1、在label右侧加入图标和弹窗
直接参看官方文档通过插槽实现
<el-form-item >
<template #label>
<div>
<span>{{ label }}</span>
<el-tooltip placement="top">
<template #content>
<div class="tip-content" v-html="tip"></div>
</template>
<el-icon class="label-icon" color="#eee949" size="16"><WarningFilled /></el-icon>
</el-tooltip>
</div>
</template>
<slot></slot>
</el-form-item>
2、解决与el-form中的rules联动的问题
要实现联动的功能,可以参考源码中的实现方式,通过查看elment plus源码可以发现下面代码片段
const formContext = inject(formContextKey, undefined)
...
const formRules = formContext?.rules
if (formRules && props.prop) {
const _rules = getProp<Arrayable<FormItemRule> | undefined>(
formRules,
props.prop
).value
if (_rules) {
rules.push(...ensureArray(_rules))
}
}
el-form通过provide 加 inject的方式向子组件传值,formContext为当前form的上下文环境,现在思路就很清晰了,只要能拿到el-form的上下文环境,就可以自己做任何处理
import { formContextKey } from 'element-plus';
// 注入父级el-form的上下文
const elForm = inject(formContextKey, undefined);
// el-form中定义的规则
const parentRules = computed(() => {
return elForm?.rules?.[props.prop] || [];
});
// 对规则进行合并,考虑到直接在el-form-item中设置required的情况,对rules进行合并
// 合并规则优先级:父级规则 > 组件自身规则 > required生成规则
const mergedRules = computed(() => {
const defaultRequiredRule = props.required
? { required: true, message: `${props.label}不能为空`, trigger: ['blur', 'change'] }
: null;
return [...parentRules.value, ...(props.rules || []), ...(defaultRequiredRule ? [defaultRequiredRule] : [])];
});
完整代码
<template>
<el-form-item :prop="prop" :rules="mergedRules" :label-width="itemLabelWidth">
<template #label>
<div>
<span>{{ label }}</span>
<el-tooltip placement="top">
<template #content>
<div class="tip-content" v-html="tip"></div>
</template>
<el-icon class="label-icon" color="#eee949" size="16"><WarningFilled /></el-icon>
</el-tooltip>
</div>
</template>
<slot></slot>
</el-form-item>
</template>
<script setup>
import { WarningFilled } from '@element-plus/icons-vue';
import { formContextKey } from 'element-plus';
const props = defineProps({
label: {
type: String,
default: '',
},
prop: {
type: String,
default: '',
},
rules: {
type: Array,
default: () => [],
},
tip: {
type: String,
default: '',
},
required: {
type: Boolean,
default: false,
},
labelWidth: {
type: [String, Number],
default: '',
},
});
// 注入父级el-form的上下文
const elForm = inject(formContextKey, undefined);
const parentRules = computed(() => {
return elForm?.rules?.[props.prop] || [];
});
// 合并规则优先级:父级规则 > 组件自身规则 > required生成规则
const mergedRules = computed(() => {
const defaultRequiredRule = props.required
? { required: true, message: `${props.label}不能为空`, trigger: ['blur', 'change'] }
: null;
return [...parentRules.value, ...(props.rules || []), ...(defaultRequiredRule ? [defaultRequiredRule] : [])];
});
// label宽度优先级:父级el-form > 组件自身
const itemLabelWidth = computed(() => {
if (elForm?.labelWidth) {
return elForm?.labelWidth;
}
return props.labelWidth;
});
</script>
<style scoped lang="less">
.label-icon {
position: relative;
top: 3px;
}
.tip-content {
max-width: 400px;
}
</style>
使用方式与原本的el-form-item相同,只是多了一个tip字段
<form-item label="数据结构" prop="dataStructure" tip="提示">
<el-select v-model="formData.dataStructure" placeholder="请选择">
<el-option label="分析报告" value="1" />
</el-select>
</form-item>
注:同理,也可以通过这种方式实现自定义组件与form中的disabled属性联动