做了这么多年后台管理系统,Element 的表单组件算是打交道最多的东西之一。早些年用 element-ui,现在切到 element plus,必填星号这个问题几乎在每个项目里都会碰到。简单说,需求无非就这几种:必填项显示红色星号、非必填项不要星号、某些字段只显示星号但不参与校验、某些字段要校验但不能让用户看到星号,还有一种是动态切换必填状态时星号要跟着变。但就这么个“小星号”,实际开发里坑还真不少,尤其是 element 内部对 required 和 rules 的自动处理机制,不摸透的话,你写了required反而校验不触发,或者明明没写required星号却自己跑出来了。
这篇文章就把 element 表单必填星号这件事从头到尾拆一遍,包含底层逻辑、不同场景的实现方式、样式定制,以及我在项目中踩过的各种坑。不管你是刚接触 element-ui 的新手,还是已经被动态表单折磨过的老手,这篇应该都能给你省点时间。
1. 先搞懂 Element 表单星号是怎么冒出来的
很多新手第一次遇到星号问题,基本都是同一个场景:我在el-form-item上只写了prop和一个输入框,啥都没配置,结果 label 前面莫名其妙多了一个红星星。一看代码,也没写required啊,这是咋回事?
这就要从 element 的源码逻辑说起。Element 的 form-item 组件内部会做一件事:解析传入的rules规则,如果检测到某条规则是必填规则(required: true),就自动给表单项标记为必填态(is-required),从而渲染出红色星号。也就是说,只要你写了类似下面这种 rules:
rules: { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }] }form-item 就会自动识别出 username 这条规则带required: true,于是乖乖把星号渲染出来。这就是为什么你“啥都没写”却出现了星号的真相——星号的开关不只是required属性,rules里的必填规则同样能触发它。
1.1 三种触发星号的写法
Element 里能触发必填星号的常见方式有三种,我直接用代码列出来:
方式一:在 el-form-item 上使用 required 属性
<el-form-item label="用户名" prop="username" required> <el-input v-model="formData.username" placeholder="请输入用户名" /> </el-form-item>这种方式最简单粗暴,直接在表单项上标记必填。它会让表单项带上is-required样式类,从而显示星号,同时在提交校验时把该字段作为必填项校验。不过要注意,required属性在 element-ui 和 element plus 里的行为略有差异,后面会专门讲。
方式二:通过 rules 规则定义
rules: { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }] }这是我的主要写法。把校验规则集中放在一个对象里维护,代码更清晰,也方便动态修改。如上所说,form-item 会自动解析 rules 里的必填规则,星号也能正常显示。
方式三:同时设置 required 和 rules
<el-form-item label="用户名" prop="username" required :rules="[{ required: false }]">这种写法会产生一些微妙的冲突,建议尽量别用。required 属性和 rules 里的 required 规则同时存在时,element 内部处理逻辑有时候会二选一,或者是先取 rules 里的规则、忽略 required 属性,或者相反,不同版本行为还不一样,容易出幺蛾子。
1.2 required 和 rules 到底什么关系
很多教程会说“required 是 rules 的简写”,这个说法在 element 里其实不完全准确。从源码角度看,form-item 在渲染时做了这样一件事:
- 收集自己身上的
required属性。 - 解析外部传入的
rules中的必填规则。 - 两者只要有一个成立,就认为这个字段需要显示星号。
- 如果 required 为 true,但 rules 里没有必填规则,校验时 element 会自动生成一条“必填”的校验规则参与校验。
所以说,required属性更像是“星号 + 必填校验”的组合开关,而rules里的required则只是校验规则之一,星号只是它带来的“副作用”。
理解这个机制非常关键。我在一个动态表单项目里就吃过亏:某个字段的校验规则是通过接口动态返回的,返回的规则里带required: false或者干脆没有 required 字段。正常逻辑下,这个字段不该有星号,但因为我之前给 el-form-item 加了required属性,结果星号一直挂着不消失,用户看着就很困惑。
所以我的建议是:如果你需要动态控制校验规则,尽量只用 rules,不要混用 required 属性。rules 的优先级和可维护性都会更好,后面第四节会专门讲动态控制。
2. 按需控制星号:显示、隐藏、只显示不校验
搞清楚星号的生成机制后,接下来就是各种“按需控制”的需求。这部分是实际开发里最常遇到的情况,我按场景逐个拆。
2.1 全局关闭星号:hide-required-asterisk
有时候表单设计图里就没有星号,但校验逻辑依然要保留必填。比如一些面向内部系统的录入页面,所有字段都是必填但不想让界面看起来全是红星,这时候就该用hide-required-asterisk。
在 el-form 上设置这个属性,可以全局隐藏所有必填星号:
<el-form ref="formRef" :model="formData" :rules="rules" hide-required-asterisk label-width="120px" > <el-form-item label="用户名" prop="username"> <el-input v-model="formData.username" /> </el-form-item> </el-form>这样即使 rules 里有required: true,界面上也不会显示星号,但提交校验时必填逻辑依然生效。
Element plus 的 el-form-item 上也支持单独的hide-required-asterisk,可以只对某一个表单项隐藏星号:
<el-form-item label="用户名" prop="username" hide-required-asterisk> <el-input v-model="formData.username" /> </el-form-item>这个属性在 element-ui 里没有,是 element plus 新增的。如果你还在维护旧版的 element-ui 项目,只能通过给当前表单项动态添加一个类名,再用 CSS 隐藏伪元素,或者用下面的自定义 label 方案。
2.2 只显示星号但不校验的玩法
有很多场景需要“视觉上的必填感”,但业务上并不强制。比如某个字段建议填写,填了更有利于审核通过,但不填也能提交。产品经理就会说:“这个字段加个星号吧,让用户知道建议填。”这时候如果直接写required,提交就直接被拦截了,根本不符合业务预期。
Element plus 提供了一个非常贴心的属性:asterisk。它只控制星号显示,不参与任何校验:
<el-form-item label="手机号" prop="phone" asterisk> <el-input v-model="formData.phone" placeholder="选填,但建议填写" /> </el-form-item>这样界面上会显示红色星号,但提交时不会有必填校验拦截。
如果你是 element-ui 老版本用户,没有asterisk属性,可以用自定义 label + CSS 伪元素实现。比如:
<el-form-item label=" " prop="phone"> <template #label> <span class="required-star-label">手机号</span> </template> <el-input v-model="formData.phone" placeholder="选填,但建议填写" /> </el-form-item>.required-star-label::before { content: '*'; color: #f56c6c; margin-right: 4px; }既然 label 是自定义的,星号位置、颜色你都能自己控制。这个方案还有个额外好处:在 element-ui 和 element plus 里都能用,算是一个通用的兜底方案。
2.3 有校验但看不见星号怎么办
反向需求也有:字段确实必填,但设计要求这里不能出现星号,甚至是这几个字段整体不能出现星号。比如密码重置页面,三个输入框(原密码、新密码、确认密码)全部必填,但页面设计为了简洁美观,不想显示任何星号。
这样处理:
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" > <el-form-item label="原密码" prop="oldPassword" hide-required-asterisk> <el-input v-model="formData.oldPassword" type="password" show-password /> </el-form-item> <el-form-item label="新密码" prop="newPassword" hide-required-asterisk> <el-input v-model="formData.newPassword" type="password" show-password /> </el-form-item> <el-form-item label="确认密码" prop="confirmPassword" hide-required-asterisk> <el-input v-model="formData.confirmPassword" type="password" show-password /> </el-form-item> </el-form>rules 里正常写required: true,校验依然生效,只是三个星号全都不显示了。注意,el-form 和 el-form-item 都支持hide-required-asterisk,并且表单项自己的配置优先级更高,可以实现局部控制。
3. 定制星号颜色、位置和样式
星号默认是红色,位置在 label 文字左侧(::before伪元素)。但 UI 设计往往不会这么乖,今天要蓝色,明天要粗体,后天要星号跑到右边去。好在这些都是纯 CSS 能解决的问题。
3.1 用 CSS 变量改星号颜色
Element plus 的星号颜色用的是主题变量--el-color-danger,默认是#f56c6c。如果只是想让所有表单星号变个颜色,最简单的做法是覆盖这个变量:
:root { --el-color-danger: #fa9d3b; /* 改成橙色 */ }但要注意,--el-color-danger是全局的,它会连带影响其他用到 danger 色值的组件(比如消息提示、表单校验报错文字等)。如果你只想改星号颜色,不想影响其他组件,更精准的做法是直接给星号伪元素写样式:
.el-form-item.is-required .el-form-item__label::before { color: #fa9d3b !important; }Element plus 里星号的 DOM 结构是 el-form-item__label 上的::before伪元素,选择器写到这个层级即可。注意加!important,因为 Element 自带样式的优先级不低,不加会被覆盖掉。
3.2 把必填星号挪到 label 右侧
国内表单多数习惯把星号放在 label 文字左侧,英文系统则相反,星号通常放右侧。Element 默认是左侧,想变右侧就这么办:
.el-form-item.is-required .el-form-item__label::before { content: '' !important; display: none; } .el-form-item.is-required .el-form-item__label::after { content: '*' !important; color: #f56c6c; margin-left: 4px; display: inline-block; }思路很简单:干掉左侧的::before,在::after里重新画一个星号。这个方案在 element-ui 和 element plus 的较新版本里都验证过,基本不会出问题。
不过有一个细节要注意:如果 label 文字本身可以换行,或者 label-width 设置得不够宽,星号跑到右侧后可能看起来跟下一个字段挨得太近,视觉上容易误读。所以调整星号位置后,最好检查一下整列 label 的对齐效果。
3.3 label-width 不够导致星号换行的坑
这个问题特别隐蔽。当 label 文字比较长,比如“自定义数据源名称”这种 8 个字的 label,而 label-width 设的是 100px 时,label 内容加上左侧星号后可能会被挤到第二行。表现出来就是星号在第一行、文字在第二行,或者文字被截断。
解决思路有三个:
- 调大 label-width,例如
label-width="130px"。 - 使用
label-position="top",让 label 和输入框上下排布,星号换行问题天然消失。 - 如果 label 长度不固定,干脆用
label-width="auto"(element plus 支持,element-ui 部分版本也支持),让 element 自动计算 label 宽度。
第三种方案在动态表单里最实用。因为动态表单的字段名经常来自后端配置,长度不可控,设置固定 label-width 很容易踩坑。用 auto 之后,由组件自己适配,省心不少。
4. 动态表单、联动校验与星号同步
动态表单大概是星号问题的高发区。比如表单引擎、低代码平台,字段配置从后端 JSON 拉下来,然后前端动态渲染。这时候星号必须跟着字段配置走,配置说必填就显示,配置说不必填就消失,而且切换过程还得顺滑。
4.1 动态配置驱动的星号渲染
假设后端返回的字段配置长这样:
const fields = [ { prop: 'name', label: '姓名', required: true }, { prop: 'age', label: '年龄', required: false }, { prop: 'email', label: '邮箱', required: true, pattern: 'email' } ];渲染时动态绑定required和rules:
<el-form :model="formData" :rules="dynamicRules" label-width="130px"> <el-form-item v-for="field in fields" :key="field.prop" :label="field.label" :prop="field.prop" :required="field.required" > <el-input v-model="formData[field.prop]" /> </el-form-item> </el-form>const dynamicRules = computed(() => { const rules = {}; fields.forEach((field) => { if (field.required) { rules[field.prop] = [{ required: true, message: `请输入${field.label}`, trigger: 'blur' }]; } }); return rules; });这种写法同时绑定了required和rules,从逻辑上看是重复了。但实际测试下来,只要required和 rules 里的必填规则保持“同真同假”,一般不会出问题。我建议保持两者状态一致,别一个 true 一个 false。
4.2 联动场景:A 选了 B 才必填
比单纯的动态渲染更复杂的是联动校验。最常见的场景:用户选择了“是否企业用户”为“是”时,“企业名称”才必填;选择“否”时,企业名称不仅不能必填,最好还把用户之前填的内容清空,避免数据残留。
处理方式是这样:
<el-form-item label="是否企业用户"> <el-radio-group v-model="formData.isCompany"> <el-radio :value="true">是</el-radio> <el-radio :value="false">否</el-radio> </el-radio-group> </el-form-item> <el-form-item label="企业名称" prop="companyName" :required="formData.isCompany"> <el-input v-model="formData.companyName" :disabled="!formData.isCompany" /> </el-form-item>const rules = computed(() => { const result = {}; if (formData.isCompany) { result.companyName = [{ required: true, message: '请输入企业名称', trigger: 'blur' }]; } return result; });注意这里有个很实际的坑:如果之前已经因为必填校验报过错,再把isCompany切回false时,即使 rules 里已经没有 companyName 的必填规则了,界面上那条“请输入企业名称”的报错信息可能还在。这时候需要手动调用clearValidate清掉:
watch(() => formData.isCompany, (val) => { if (!val) { formRef.value.clearValidate('companyName'); } });4.3 更新 rules 后星号不同步的解决方法
动态修改 rules 后,星号有时候不会立刻跟着变。这是我在实际项目里遇到的另一个经典问题。原因是 el-form-item 对 rules 的解析结果有缓存,或者说是响应式依赖追踪的时机问题。
遇到这种情况,最简单的解决办法是给 el-form-item 加一个动态的key,强制它重新渲染:
<el-form-item :key="field.prop + '-' + field.required" :label="field.label" :prop="field.prop" :required="field.required" > <el-input v-model="formData[field.prop]" /> </el-form-item>key 一旦变化,Vue 会销毁旧组件、创建新组件,星号和校验状态自然全部刷新。这是最保险的做法,唯一的代价是组件重建会导致输入框内容暂时丢失,所以只推荐在字段配置本身变化时用。
如果只是星号显示不同步,但输入值不能丢,那可以不重建组件,改用下一节里的 CSS 方案强制刷新显示状态,或者检查一下是不是 form-item 的 required 属性绑定值没有正确响应。
5. 常见问题与排查实录
这部分整理我在实际开发中真正踩过的坑,每个都有具体场景和解决方案。如果你正准备交付表单功能,建议把这节通读一遍,能帮你少加好几个班。
5.1 必填校验没生效但星号还在
现象:星号正常显示,但提交表单时空值也能通过校验,或者必填校验压根没触发。
排查思路:
- 先看 el-form 上有没有写
:model,并且 el-form-item 上有没有写prop。缺一个,校验都找不到字段。 - 再看 el-input 上有没有写
v-model,并且绑定的属性名和 prop 是否一致。不一致的话,校验作用在 A 字段,用户输入在 B 字段,永远校验不出问题。 - 最后检查 el-form 的
rules是不是写在子组件里了。很多人在子组件里定义 rules,但 el-form 在父组件里,导致 rules 没传进去。这种情况下星号有时能显示、校验却不生效,非常坑。
星号显示但校验不生效,根源往往是“星号是手动 required 强行显示的,但 prop 或 rules 没配对”。最简单的验证方法:把 el-form-item 的 required 属性去掉,只留 rules,看星号是否还在。如果星号消失了,说明 rules 里的必填规则没有传递到该表单项,重点检查 prop 是否匹配。
5.2 resetFields 和 clearValidate 用不对
先说结论:
resetFields:把整个表单重置到初始值(不是清空!),并移除所有校验状态。clearValidate:只移除校验状态(报错信息),不改变值。
很多新手的误区是把 resetFields 当“清空表单”用。比如用户点“新增”按钮,期望把表单变成全空,然后调用了 resetFields,结果发现表单里还有上一次的数据。原因就是 resetFields 是把字段重置为表单挂载时的初始值,而不是空值。如果初始值是{ name: '张三' },reset 之后 name 还是张三。
要真正清空表单,需要手动把 model 里的所有字段置空,再调用 clearValidate:
function resetForm() { Object.assign(formData, { name: '', age: null, email: '' }); formRef.value.clearValidate(); }还有一点:resetFields 只能重置在 el-form-item 上配置了 prop 的字段。没配 prop 的字段不在表单校验体系里,resetFields 管不着。
5.3 el-date-picker 时间范围校验怎么写
热搜词里有一条“el-date-picker判断结束时间大约起始时间”,应该就是开始日期和结束日期的校验问题。这里的难点是:既要限制用户选择范围(日期选择器上禁用不合法日期),又要在提交时校验结束时间大于开始时间。
我常用的写法是自定义 validator:
<el-form-item label="开始日期" prop="startDate"> <el-date-picker v-model="formData.startDate" type="date" placeholder="请选择开始日期" :disabled-date="disabledStartDate" /> </el-form-item> <el-form-item label="结束日期" prop="endDate"> <el-date-picker v-model="formData.endDate" type="date" placeholder="请选择结束日期" :disabled-date="disabledEndDate" /> </el-form-item>const disabledStartDate = (date) => { if (!formData.endDate) return false; return date.getTime() > new Date(formData.endDate).getTime(); }; const disabledEndDate = (date) => { if (!formData.startDate) return false; return date.getTime() < new Date(formData.startDate).getTime(); };校验规则里再加一个自定义校验器:
const validateDateRange = (rule, value, callback) => { if (!formData.startDate || !formData.endDate) { callback(); return; } if (formData.endDate < formData.startDate) { callback(new Error('结束日期不能早于开始日期')); } else { callback(); } };两个角度双保险:用户选择阶段用 disabled-date 限制可选范围,提交阶段用 validator 兜底。disabled-date 只能防止用户点击不合法日期,但程序赋值或初始化时可能绕过它,所以 validator 不能省。
5.4 element plus 表格偶发阴影问题
这个跟表单星号关系不大,但既然热搜词里提到了 element plus 2.11.4 版本表格偶尔出现莫名阴影,我就顺带说一句。这类问题多半是固定列(fixed)在横向滚动时产生的阴影残留,属于组件样式 bug。排查顺序是:先看是不是固定列阴影,再试去掉 fixed 属性看是否复现,最后确认版本号,看官方 changelog 有没有修复。如果项目锁定了版本,紧急处理方式是用 CSS 覆盖固定列阴影的 z-index 或背景色。
5.5 前端校验不是全部:后端校验不可省
最后想提醒一句,Element 表单校验做得再好,也只是客户端的体验优化,不能作为数据安全的唯一防线。之前遇到过一个表单提交场景,前端明明校验了必填,结果接口直接拿过去,有人绕过页面调用接口,照样能把空数据写进数据库。后来检查下来就是后端接口没做必填校验,被当作“存储型 XSS 的风险入口”通报了。所以前端校验负责体验,后端校验负责安全,两者不能互相替代。必填、格式、长度这些规则,后端接口上该判还得判。
6. 我的一些实操心得
做了这么多表单相关的功能,我个人的体会是:Element 的星号机制不复杂,但它的触发源不止一个,导致很多人对它的理解停留在“玄学”层面。其实只要记住两件事,大部分问题都能解决:
一是星号本质是 is-required 样式类渲染出来的伪元素,凡是能让表单项进入 required 状态的操作,都会显示星号。二是校验逻辑和星号显示是可以解耦的,一个管视觉,一个管提交,两者在复杂业务里不一定同步,需要你根据场景主动控制。
最后再分享一个小技巧:如果你的表单是“绝大部分必填、极个别选填”,可以考虑用hide-required-asterisk全局隐藏星号,然后在选填字段上自定义一个“选填”标签。这样做的好处是界面干净,同时暗示用户大部分字段都要填,比满屏红星更优雅,也是在真实项目里被验证过体验不错的方案。
这段代码不算多,但你真正用到动态表单、联动校验的时候,就会明白掌握星号背后那套机制,比死记几个属性值重要得多。表格呈阴影、时间校验这些看上去无关的坑,其实都和一个核心问题挂钩:Element 组件不是单纯的“标签 + 输入框”,它内部有自己的状态逻辑,只有理解了这些逻辑,才能在边界场景里少走弯路。希望这篇文章能给你在表单星球上省下一点排查的时间。