Element UI各种使用问题汇总(Input、Form篇)

Input回车导致页面刷新的问题

Element UI为了遵守W3C规范特意设置的,就是当Form中只有一个Input的时候,Form把这个事件当成了是提交表单的操作,所以页面会刷新。

解决方法:

凡是<el-form>里面只有一个Input,就在<el-form>上附加一个事件:@submit.native.prevent,这样可以阻止默认提交,可以解决这个问题。

Input的各种输入约束办法

可以使用这几种约束方法:

  1. 正则替换,比较粗暴,慎用

一些场景下,比如编辑人员录入一篇文章,早就规定了标题不允许使用叹号,编辑也明白这个规定,他只是从别的地方复制标题,恰好标题带叹号,那么,正则替换可以帮助编辑直接把叹号去掉。比如:

复制代码
@input="form.title = form.title.replace(/[!!]/g,'')"

这就要比提示"标题禁止出现叹号"要好。

同样的,约束一个输入框只允许输入数字和小数点,也可以用这种方法。

记住:

不要用这种方法去执行超出常识的约束和有负面作用的约束,因为不见得使用者会注意到程序的自动替换,或许会造成使用者困扰甚至造成事故。

  1. 长度约束,阻止输入,也比较粗暴,慎用

maxlengthminlength这2个属性可以用来约束字符长度,可用于type="text"type="textarea",但是对于别的type它一般无能为力,比如改成type="number"就不起作用。

注意,minlength在Element UI中其实是无效的,至少经过我测试,
minlength + rules设为必填 + this.$refs["form"].validate()是无效的,无法报错。
minlength其实在原生Form中才会有效。

长度控制可以设置比如某个网络游戏昵称(最多6位)......等。

事实上,我maxlength并不建议用于控制手机号输入,尽管国内手机号一定是11位,但是用rules控制会更好一些,因为总有人无意输入了12个数字,你粗暴的阻止最后一位的输入也可能会造成困扰。

真的想控制长度,最佳实践是使用rules,因为控制数值长度是不常见的约束,无论是用正则替换还是用阻止输入,都可能让使用者困扰或者不留意,最终形成事故。

记住:

同样的,不要用这种方法去执行超出常识的约束和有负面作用的约束 ,因为不见得使用者会注意到输入被截断,比如你设置maxlength="10",一个使用者飞速地敲了11位字符,他根本没注意最后一个字符是没有成功输入的,也会造成使用者困扰甚至造成事故。

maxmin一般跟step联合使用,但是它不具有约束性,只要你愿意,你可以敲出高于max的数值,所以尽量别用maxmin

  1. 最妥帖的办法,使用rules

官方文档:https://element.eleme.cn/#/zh-CN/component/form

基于async-validator:https://github.com/yiminghe/async-validator

比如,手机号必须11位,写法是:

复制代码
      formRules: {
        ownerPhone: [
          { required: true, message: "手机号不能为空", trigger: "blur" },
          { len: 11, message: "手机号应为11位", trigger: "blur" }
        ],
      }

一个输入行,有多个输入框,怎么分别验证,分别提示?

很简单,让<el-form-item>里面继续嵌套<el-form-item>即可,外层<el-form-item>只负责写label,不写prop,内层的<el-form-item>不写label,写prop。

验证提示会显示在每个内层<el-form-item>的下方。

如何移除某个输入框的验证结果?

比如有2个输入框,前者如果是后者的2倍则通过。

现在我前面填写了10,后面填写6,验证出错,这时候后者报红字错误。我发现是前者写错了,改成了12,这样符合了要求,但是后者的红字错误不会消失,怎么办?

只能手动清除某个输入框的报错,做法是:

  1. <el-form-item>ref,值可以是字段名。

  2. 在validator末尾写上this.$refs.xxx.clearValidate()即可。

明明写的是数字,为什么type: 'number'验证不通过?

首先,给v-model加上.number

然后,服务器返回的字段,必须转换为数字再复制给form。

如何快捷生成表单的rules?

当一个表单快写完了,就该写rules,但是这玩意写起来很烦,内容大部分一样,但是键名都各有区别,比如:

复制代码
      rules: {
        isOwner: [
          { required: true, message: "是否户主不能为空", trigger: "blur" }
        ],
        phoneNumber: [
          { required: true, message: "联系电话不能为空", trigger: "blur" },
          { len: 11, message: "手机号应为11位", trigger: "blur" }
        ],
      },

大部分都是xxx不能为空这类话。有没有快捷生成办法?

我本来打算写一个工具来专门生成,后来觉得没必要,因为没办法特别的智能,所以简单生成一下,然后再做加工就好了。

方案:

  1. 创建@/utils/genCode.js,写入:

    module.exports = {
    rules: function (form) {
    let rules = {};
    for (let key in form) {
    if (Array.isArray(form[key])) {
    rules[key] = [{ required: true, message: "不能为空", trigger: "blur", type: 'array' }];
    } else {
    rules[key] = [{ required: true, message: "不能为空", trigger: "blur" }];
    }
    }
    console.log(JSON.stringify(rules, null, 2).replace(/"(.+?)":/g, '$1:').replace(/"/g, "'"));
    },
    };

  2. 在main.js写入:

    if (process.env.NODE_ENV === 'development') {
    Object.defineProperty(Vue.prototype, '$genCode', { value: require('@/utils/genCode') });
    }

使用:

  1. 在提交按钮事件最顶部加上这2行代码:

    复制代码
       this.$genCode.rules(this.form);
       return;
  2. 在浏览器里把必填的字段都填上随意字符,不必填的字段如果你不想设rule,可以不填。

  3. 点击提交按钮,会打印出来一片代码,将代码贴到编辑器,然后进一步修改即可。

  4. 用完删掉this.$genCode.rules(this.form);return;这2行就完事。

如何快捷生成表单的'data.form'?

首先问自己懒不懒,如果懒,可以直接给data.form赋值为{},只要Form中没有Checkbox-Group就行,因为Checkbox-Group初始化的时候就要读取data.form中的属性,而且必须是数组,所以{}这种空对象会报错。Element UI剩下的所有组件都没问题,哪怕是级联选择器这种value一定是数组的组件,也不会报错。

如果你比较勤快,希望代码正规一点,可以模仿上一道题的方法。

第一步,看Form里有没有Checkbox-Group,有的话将它的属性先写好,比如现在是:

复制代码
form: {
  xxcheckboxList: [],
}

第二步,创建@/utils/genCode.js,写入:

复制代码
module.exports = {
  form: function (form) {
    let data = {};
    for (let key in form) {
      if (Array.isArray(form[key])) {
        data[key] = [];
      } else if (typeof form[key] === 'string') {
        data[key] = '';
      } else if (typeof form[key] === 'boolean') {
        data[key] = form[key];
      }
    }
    console.log(JSON.stringify(data, null, 2).replace(/"(.+?)":/g, '$1:').replace(/"/g, "'"));
  },
};

第三步,在main.js写入代码,跟上题内容一样。

第四步,在提交按钮事件最顶部加上这2行代码:

复制代码
      this.$genCode.rules(this.form);
      return;

第五步,必填不必填的字段一律填,然后点击提交按钮,剩下的不说了。用完别忘记注释掉。

弄懂Form的resetFields方法原理,并谨慎使用

resetFields方法原理

Form的resetFields方法的用法是this.$refs[refName].resetFields();,这个方法做的事就是将表单的某些 数据(为什么说是"某些"请继续看)重置为初始值(即data加载的时候form的值),重置过程大致是:

  1. 先找Form身上设置的model,也就是表单数据对象,比如叫form

  2. 再找Form里的各个<el-form-item>,看它身上的prop,比如payName

    复制代码
       <el-form-item label="XX设置" prop="payName">
         <el-input
           v-model="form.payName"
         />
       </el-form-item>

然后将payName对应的form.payName设为data里的form初始化的时候的值,初始化的时候没有这个属性的话,则重置为undefined或者[null]。依次处理每个<el-form-item>。如果某个<el-form-item>没有prop,那么也不会有属性被重置。

看到了吗?

  1. 没有被prop的属性,resetFields就管不到,不会对其做任何操作。

  2. v-model="form.payName"resetFields完全没关系!

如果不用resetFields还能用什么方案?

可以重置为空对象:this.form = {};或者重置为this.form = {xx: []}这种带属性的对象。

两种方案的对比如下:

resetFields方案 手动重赋值方案
依赖$ref 依赖 不依赖
方案执行时机 Form元素必须渲染才能执行,也就是说$refs必须能取到值 可以在Dialog任何周期钩子里执行重赋值,也可以在触发弹出Dialog的函数里执行(通常用这种方案)
必须有prop="属性名" 必须 不必
this.form初始值 只在dataform里设置 form和重赋值语句中都要设置

可见:resetFields方案的顾虑多,重赋值方案的顾虑少(但不是没有顾虑),只需要关注重赋值初始值即可,但假如初始值有属性,却无脑重赋值成了{}也会出事故。

慎用resetFields的场合

假设有个搜索查询表单,初始值是:

复制代码
      queryParams: {
        pageNum: 1,
        pageSize: 10,
      },

有一个选填的Select,我选择了一个值,也就是form.a设为1,然后Select的change事件函数根据这个值计算出了另一个值form.b,比如是乘以100的关系,于是form.b得到100,然后,我点击了提交,服务器要的是form.b而不是form.a100被成功提交,到目前没啥问题。

接着,我点击"重置"按钮,此时用resetFields来重置表单,其结果是:

  • form.a被重置为undefined,因为Select设置了prop="a"

  • form.b没任何变化,因为没有哪个prop设置为b,所以form.b此时还是100

现在,表面上看,表单都被重置,Select的值也是空,但是,form.b的值依然没有变,是100。紧接着,重置按钮进行下一步操作,也就是根据queryParams重新请求数据,此时queryParams是这样:

复制代码
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        b: 100,
      },

到此,问题出现:我本意是重置所有搜索条件,但是b并没有被重置,查询结果也一定是错的。

解决方案:

其实这事解决起来很容易,我列出3种方案,你可以选择一种你认为最无脑、最不容易出错的方案。

方案1:queryParams初始值加入b: null

方案2:取消change事件监听,改为submit的时候才计算所有的b,但是要注意,不要给queryParams.b赋值,不然又成了无法resetFields,应该单设变量b,参数传(...queryParams, b)

方案3:直接不用resetFields,改为手动重赋值为初始值。

我个人推荐方案1或方案3,理由是:

  1. 方案1是只要哪个属性没在prop就一律写到初始值,比较无脑,但可能会忘了写。

  2. 方案2将计算放到提交事件里,注意事项比较多,也要用到解构赋值,也不利于封装统一的提交函数。

  3. 方案3手动赋值,好处是初始值不用考虑b,重赋值也不用考虑b,重赋值无脑保持跟初始值一样即可。如果初始值是空对象,那就更好了,无脑重赋值为空对象就行了。

Select要给form的多个属性赋值,如何写代码?

我们用:value=""给Option指定输出的value值,但如果想一次性给form的多个属性赋值,该怎么办呢?比如又要传递userId,又要传递nickName,怎么办呢?(这里我假设你已经知道nickName有可能不会被重置的风险以及解决办法。)可以这样写@change

复制代码
        <el-form-item label="人员" prop="userId">
          <el-select v-model="form.userId" placeholder="请选择人员"
            clearable
            :style="{width: '100%'}"
            @change="form.nickName = personList.find(v=>$event===v.userId).nickName"
          >
            <el-option v-for="(item, index) in personList" :key="index"
              :label="item.nickName"
              :value="item.userId"
            ></el-option>
          </el-select>
        </el-form-item>

其中$event指向Select的值,也就是form.userId

当然,如果后端除了userIdnickName,还要其他值,那么我建议写一个事件函数比较清晰。

Date-Picker或者Time-Picker加上is-range的话,值是数组,怎么拆成form的2个属性?

这要比上个问题好解决,值是数组的话,可以在@change里直接解构赋值,这样就拆成了form的2个属性。(这里我假设你已经知道startTime、endTime有可能不会被重置的风险以及解决办法。)

复制代码
  <el-time-picker
    is-range
    v-model="form.range"
    range-separator="至"
    start-placeholder="开始时间"
    end-placeholder="结束时间"
    placeholder="选择时间范围"
    @change="[form.startTime, form.endTime] = $event"
  >
  </el-time-picker>

有个小疑问

那么,上一个问题(《Select要给form的多个属性赋值,如何写代码?》),为啥我们不把Select的值直接设为item,然后把item解构呢?

因为下面这种写法,在编辑器里是报错的,原因是form.nickName不能是这种表达式,必须只能是变量名。

复制代码
    @change="{nickName: form.nickName} = $event" // 非法写法

还有人会问,写成@change="form.nickName = $event.nickName"不就行了么?

确实,写法是没错,但是用法有悖论。

一、value是item对象,意味着服务器可以收到这个对象,为什么还要拆分呢?

二、如果说,后端不喜欢处理对象,就喜欢拆分传送,那么,你不仅要写form.nickName = $event.nickName,你还要写form.userId = $event.userId,等于要写2行语句,这种情况下就应该专门定义方法了,并不省事,对不对?

三、当你不是新建表单,而是编辑表单的时候,你就要给Select的Option的value传递对象,以便Select能在UI上正确显示当前值。这时候,你就要找后端给你返这个现成的对象,或者自己拼出这个对象,不然,Select是不会显示当前值的。并不省事,对不对?

最后提醒一句,绑定对象类型的话,官方说:

如果 Select 的绑定值为对象类型,请务必指定 value-key 作为它的唯一性标识。

动态表单字段

所谓"动态表单",有2种解释,一种是根据data动态生成输入框,一种是在模板写死代码,用v-show来控制输入框。今天说后一种。

v-show来控制输入框的前提下,提交的时候应当判断到底提交哪些字段,有些字段就应该不提交。比如:

复制代码
let params;
if (['0','1','2'].includes(this.form.deptType)) { // 部门类型为0/1/2时
  const { lng, lat, ...rest } = this.form; // 忽略经纬度
  params = rest;
} else if (this.form.deptType === '3') { // 部门类型为3时
  params = this.form; // 全要
} else if (this.form.deptType === '4') { // 部门类型为4时
  const { deptAddress, deptDesc, lng, lat, ...rest } = this.form; // 忽略地址、描述、经纬度
  params = rest;
}
// 接下来提交params即可。

动态修改rules

比如,某些前提下a字段不必填,另一些前提下a字段必填,怎么办?

  1. rules要把字段写全,即使有些字段的requiredfalse。总之按照默认状态设置truefalse,但字段一定要写进去。

  2. 在触发动态变化的事件里修改rules

    @change="onChangeDeptType"

  3. 凡是可能变化的字段都要加上。比如:

    复制代码
     onChangeDeptType(value) {
       if (['0','1','2'].includes(this.form.deptType)) {
         this.rules.lat[0].required = false;
         this.rules.lng[0].required = false;
         this.rules.deptAddress[0].required = true;
         this.rules.deptDesc[0].required = true;
       } else if (this.form.deptType === '3') {
         this.rules.lat[0].required = true;
         this.rules.lng[0].required = true;
         this.rules.deptAddress[0].required = true;
         this.rules.deptDesc[0].required = true;
       } else if (this.form.deptType === '4') {
         this.rules.lat[0].required = false;
         this.rules.lng[0].required = false;
         this.rules.deptAddress[0].required = false;
         this.rules.deptDesc[0].required = false;
       }
     }

Input的问号注释

我们在别的UI框架中可能见过这样的问号注释,鼠标悬停能弹出气泡内容:

image.png

但官方手册并没有介绍这种用法,只是说,可以在Input里插入图标。今天我就来变相实现输入框的问号注释。

复制代码
              <el-input v-model="form.nickName" placeholder="请输入昵称">
                <el-popover
                  slot="suffix"
                  placement="top"
                  title="标题"
                  width="200"
                  trigger="hover"
                  content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">
                  <i slot="reference" class="el-icon-question color-000"></i>
                </el-popover>
              </el-input>
  1. 官方手册是插入了一个问号Icon,但是今天我们不但是要插入Icon,还要鼠标悬停能弹出气泡,所以我插入了一个Popover。

  2. Popover里面又插入了一个Icon,是问号图标。这样就OK了。

  3. 如果觉得图标颜色不明显,可以设个色值,比如color-000是我自定义的color: #000;,你可以随意改。

最后是这样子:

image.png

另外,你可能在有些网站看到的图标是叹号,我认为叹号和问号的区别在于:

  • 当用户很大概率不知道怎么填内容的时候,用问号。

  • 当用户确定知道怎么填,只是想看更多的注释的时候,甚至是起警告作用的时候,用叹号。

如何避免发送form的临时/私有属性?

比如Input组件的值是form.a,由它计算出一个form.b是服务器需要的值,但是服务器不需要form.a,你的一个做法是把a和b都传上去,但是可能后端程序员会不高兴,因为无用字段多了他会烦,对前端程序员也不太好,因为参数当然是越少越好。

你可以使用剩余操作符来赋值:

复制代码
      const {a, ...restParams} = this.form;
      postXXX(restParams).then(response => {
        // ...
      })

这样的话,form.a就被排除了,也不会被上传了,并且不会影响form的数据。

但是,这并不是最佳实践!最佳实践是将临时属性加一个私有标记,比如_a,也就是前置下划线,发送的时候用axios的拦截器统一拦截,过滤掉它。

所以上例Input组件的值改为form._a,而form.b不动。

怎么过滤form._a,只上传form.b

在axios的请求拦截器里过滤,凡是属性以_为首字符的,就不要发送。

复制代码
service.interceptors.request.use(
  config => {
    if (config.params && Object.keys(config.params).some(v => v[0] === '_')) {
      let newParams = {};
      for (let key in config.params) {
        if (key[0] !== '_') {
          newParams[key] = config.params[key];
        }
      }
      config.params = newParams;
    }
    if (config.data && Object.keys(config.data).some(v => v[0] === '_')) {
      let newData = {};
      for (let key in config.data) {
        if (key[0] !== '_') {
          newData[key] = config.data[key];
        }
      }
      config.data = newData;
    }
    return config
  },
  error => {
    Promise.reject(error)
  }
)
相关推荐
Henrii_历小海7 分钟前
WhatsApp Web 不是手机镜像:多设备协议、本地数据流与出海获客流水线
前端·智能手机
ayqy贾杰15 分钟前
Claude Fable 5 提示词泄漏,抓紧学习下
前端·后端·面试
To_OC8 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志9 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯11 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能