[Vue] 跟着 Vue 闯荡前端世界 – 12 使用 vee-validate 进行多语系表单数据验证

表单验证是每个网站必备的工作项目,本文使用 vee-validate 实践表单验证,并搭配 vue-i18n 语系档来打造友善的多语系错误提示环境。


前言


前端检核基本上套用 vee-validate 就搞定了,本文除了介绍 vee-validate 验证机制外,还会以笔者近期的开发经验分享如何套用检核语系档,并整合系统提供给 vue-i18n 语系档至错误消息中来显示字段名称。

vee-validate v2.0.3

基本设定


开始前先了解一下我们对检核的需求有哪些,后续会针对各项目进行说明。

  1. 触发显示错误提示的时机
  2. 多国语系 (中英字段名称带入错误提示中)
  3. 覆写特定检核的错误提示文字
  4. 自行定义检核逻辑

首先当然是安装套件

npm install vee-validate --save

接着以最小配置来进行设定 (setupVeeValidate.js),可以在这个阶段我们可以决定的项目如下:

  • 初始语系 (locale)
  • 触发检核的时机 (events)
  • 默认各语系检核失败提示的文字 (dictionary)
/* setupVeeValidate.js */

import Vue from 'vue'
import VeeValidate from 'vee-validate'
import localeService from 'services/localeService'

// support lang
import tw from 'vee-validate/dist/locale/zh_TW'
import en from 'vee-validate/dist/locale/en'

// global config
const config = {
  // 初始语系 (tw / en)
  locale: localeService.getCurrentLang(),
  // 触发检核的时机(输入or离开输入框)
  events: 'input|blur',
  // 默认各语系检核失败提示的文字 (需要对应 locale 设定)
  dictionary: { tw, en }
}

Vue.use(VeeValidate, config)

依照笔者的习惯会将语系相关逻辑收至 localeService 服务中,避免类似逻辑散落四处,此服务主要负责“封装默认语系判断逻辑”、“取得当下语系”及“切换语系”的工作,以下就参考参考即可。

/* localeService.js */

import { Validator } from 'vee-validate'
import i18n from 'setup/setupLocale'

/
 * @description 切换网站语系
 */
const switchLang = (newLang) => {
  const isChange = getCurrentLang() !== newLang
  if (isChange && (newLang === 'en' || newLang === 'tw')) {
    i18n.locale = newLang
    Validator.localize(newLang)
    window.localStorage.setItem('locale', newLang)
  }
}

/
 * @description 取得默认(当下)语系
 */
const getCurrentLang = () => {
  const locale = window.localStorage.getItem('locale') || 'tw'
  window.localStorage.setItem('locale', locale)
  return locale
}

export default {
  switchLang,
  getCurrentLang
}

套用检核


这个套件本身就已经有提供一些常用检核逻辑,像是必填资讯、数字检查等逻辑可以直接套用,因此我们可以先套用这些默认检核来测试一下功能是否正常运行;而套用的方式只需要在 input 元素中加入 v-validate directive 来设定所需的 validator 名称及参数即可,以下是一个简单的范例。

  1. 定义 name 作为检核识别码,以 name 作为 key 值来存放检核的错误资讯。
  2. 定义 validators 来为 v-model 数据做检核,检核顺序从左至右以 "|" 符号区隔,并可传入参数。 

测试一下,当在输入数据或移开输入框 (input / blur) 时会触发“必填”及“最少输入文字长度”的检核,而默认的错误消息会将字段 name 套入其中做显示。

若无多国语系的需求可以简单透过 data-vv-as 属性定义这个字段的显示名称 ( ex. data-vv-as="使用者名称" ) ,这样错误消息中的 userId 就会被置换成 "使用者名称",以此获得完整的错误提示消息。

送出数据前需检查每个字段是否皆通过检核,此时可使用 this.$validator.validateAll() 方法来检查每个字段,只有在全部通过的时候才会将数据送出,示意代码如下。

/* SetUserIdForm/index.vue */

export default {
  name: 'SetUserIdForm',
  methods: {
    submit: async function () {
      const isValid = await this.$validator.validateAll()
      if (isValid) {
        // call api to submit data
      }
    }
  }
}

套入字段名称语系


在一个具有多国语系的网站中,输入字段前方一定会有字段名称来说明此输入框的作用,而这个字段名称会对应到语系档中的 key 来做正确语系文字的呈现,因此我们的目标就是要让 vee-validate 套上这个语系档,当字段名称对应到语系档时可以自动套入语系档中的文字至错误消息中。实践方式请参考以下资讯。

目前网站提供给 vue-i18n 的语系档如下

/* tw/lang.js */
export default {
  __userId: '使用者名称',
  __password: '固定密码',
}
/* en/lang.js */
export default {
  __userId: 'User Name',
  __password: 'Password',
}

依 vee-validate 所定义的结构建立 veeValidateDic.js 文件,将这些中英语系档作为 attributes 数据传入。

/* tw/veeValidateDic.js*/

import attributes from '/tw/lang'

export default {
  // 对应到输入元素的name名称  or 
  // 在上方 error message 的 field 就会自动取代成"中文"语系档中 key 为 __userId 的文字
  attributes
}
/* en/veeValidateDic.js*/

import attributes from '/en/lang'

export default {
  // 对应到输入元素的name名称  or 
  // 在上方 error message 的 field 就会自动取代成"英文"语系档中 key 为 __userId 的文字
  attributes
}

开启 setupVeeValidate.js 配置文件将上述文件引入 vee-validate 全域 config 中

最后只要将 name 改为语系档中表示这个字段名称的 key 值即可。

从结果中发现 input name 设定的 __userId 值已经可以 mapping 到中文语系档中的“使用者名称”文字,并且顺利套入到错误提示中了。

覆写错误提示文字


当默认检核所提供的错误提示消息不符合需求时,我们可以考虑覆写它;例如以下情境,客户觉得应该把“不能小于”调整为“长度须超过”比较符合期待,此时可以透过以下方式进行调整。

开启先前所定义的 veeValidateDic.js 配置文件,新增 messages 对象来针对特定 validator 覆写其错误消息,可以将字段显示名称 (field) 及 validator 参数 (args) 套入消息中,开发人员可以依照需求灵活调整消息内容。

/* tw/veeValidateDic.js*/

import attributes from '/tw/lang'

export default {
  messages: {
    // 覆写原有 validator 错误消息
    min: (field, args) => `${field}长度需超过${args[0]}符!`
  }

  // ... 略 ...
}

错误消息就会依照设定显示

必填检核的消息调整技巧

笔者在开发过程中发现必填 (required) 的检核错误消息并不适合全部情境,因为可能会套到 input / select / check 等型态的输入字段中,若在核取方块下方显示“请输入 XXX”还是满奇怪的。

本来考虑建立一个新的检核逻辑来处理,但发现进入自定义检核的先决条件就是必须存在数据 (有值才会进入检核逻辑),因此绕回原点自行传入参数来决定 required 提示消息 (请输入、请勾选、请选取....) 啰!

/* tw/veeValidateDic.js*/

export default {
  messages: {
    // 第二个参数 args 为验证方法后面设定的参数, 为数组(可用逗号区隔多笔参数)
    // 如验证消息需"请选择XXXX", 可加入参数 v-validate="'required:choose'"
    // 如验证消息需"请选取XXXX", 可加入参数 v-validate="'required:select'"
    required: (field, args) => {
      if (args && args.length === 1) {
        switch (args[0].toString()) {
          case 'select':
            return `请选取${field}`
          case 'choose':
            return `请选择${field}`
          case 'check':
            return `请勾选${field}`
          default:
            return `请输入${field}`
        }
      } else {
        return `请输入${field}`
      }
    }
  }
}

自定检核逻辑


每个表单或多或少都有一些特殊的逻辑需要被检核,并且有重复被使用的机会,因此笔者会将检核类的逻辑统一都收在 validators 数据夹中,无论是否搭配 vee-validator 做字段检核,亦或者在程序中都可以重复参考相同的检核逻辑;而笔者会在这个数据夹中透过 index.js 来动态 export 数据夹中的所有 validator 出来,这样当新增新检核逻辑时就不需要异动太多地方。

/* Dynamic Exporter:
 * Dynamically export all files (except self) in current folder
 */
const req = require.context('.', false, /.js$/)

req.keys().forEach((key) => {
  const name = key.replace(/^./(.*).js/, '$1')

  if (name !== 'index') {
    module.exports[name] = req(key).default
  }
})

载入自定检核逻辑

再来将所有于 validator 数据夹下的检核逻辑都透过 Validator.extend() 方法扩充到 vee-validate 中,而 validator 名字就是文件名,接着就可以专心针对每个特定的检核来撰写逻辑了。

撰写自定检核逻辑

首先,试想一个设定用户名称的情境,用户名称需由【英文】+【数字】组合而成,因此我们可以在 validator 数据夹中建立一个 requireAlphaNum.js 文件来检查此逻辑;另外,又希望不要跟特定数据 【身份证字号】相同,因此再建立一个可以接受参数的 noSameAsCustId.js 来做检核。

/* validator/requireAlphaNum.js */

export default (value) => {
  // 需包含英数字
  return /^(?:[0-9]+[a-z]|[a-z]+[0-9])[a-z0-9]*$/i.test(value)
}
/* validator/noSameAsCustId */

export default (value, args) => {
  // 不可与身份证字号相同
  let custId = args[0]
  if (custId) {
    return value.toUpperCase().indexOf(custId.toUpperCase()) === -1
  }
  return true
}
可以接收字段值 (value) 及传入 validator 的参数 (args) 作为检核判断依据

设定错误提示文字

订出新的 validator 当然也要设定对应的错误提示文字,而这部分如同覆写默认检核逻辑错误提示文字,只要在先前所定义的 veeValidateDic.js 配置文件中,于 messages 对象中新增自订 validator 错误消息格式即可。

/* tw/veeValidateDic.js*/

export default {
  messages: {
    // ... 略 ...
    requireAlphaNum: (field) => `${field}需包含英文与数字`,
    noSameAsCustId: (field) => `${field}不可包含身份证号`
  },
  // ... 略 ...
}

套上自定检核逻辑

最后将新增的两个自订 validator requireAlphaNum 及 noSameAsCustId 加入 v-validate 属性中即可,注意noSameAsCustId 需要传入 custId 参数作为比较是否与身份证号相同之用。

测试一下这两个检核逻辑无误就可以收工了

例外的检核失败消息


原则上相同 validator 只会存在一种检核失败提示文字,但有时候相同的检核逻辑套用在不同输入框会需要呈现不一样的提示消息,这种情境笔者称之例外的检核失败提示消息;在 vee-validator 中提供一个方式可以针对特定 input name 指定特定 validator 来显示特定 message 文字。

可于 veeValidateDic.js 配置文件中,新增 custom 对象来针对特定 validator 调整错误消息格式。

  1. 检核字段所设定的名称 (input name)
  2. 该字段所需调整的 validator 名称及调整后的检核失败提示文字

在符合上述条件 input name 为 __userId 时,当 required 检核失败就会依照 custom 设定做显示;其他不符合条件的检核,就会依照默认或通用性 message 设定来做显示。

合并字段检核


有时候数据会分散在数个输入字段中 (ex. 信用卡有效年月),可能年份是一个输入框,月份又是一个输入框,最终须依据年份与月份检核是否已过期,因此针对这类检核的方式如下。

  1. 检核月份 month 
  2. 检核年分 year 
  3. 检核年分加月份,通常会定义一个 computed property 来合并数据,如本例 fullCardValidDate 数据,接着在 v-validate 中设定 fullCardValidDate 来针对其内容值做 creditCardValidDate 自订逻辑检核

由于 v-validate 会自动查看是否有设定 v-modal 资讯并对该值做检核,因此不必设定为  形式;但当检核目标为特定 computed property 时,就必须明确指定需检核哪一个计算属性。

参考代码


以下为各配置文件的示意范例,有兴趣的朋友可以试着玩看看就会有比较深刻的印象。

初始套件配置文件

/* setupVeeValidate.js */

import Vue from 'vue'
import VeeValidate, {Validator} from 'vee-validate'
import localeService from 'services/localeService'

// support lang
// https://github.com/baianat/vee-validate/tree/master/locale
import tw from 'vee-validate/dist/locale/zh_TW'
import en from 'vee-validate/dist/locale/en'

// 自定义的语系文字配置文件
import twVeeValidateDic from '../i18n/tw/veeValidateDic' 
import enVeeValidateDic from '../i18n/en/veeValidateDic' 

// 扩充自定义 validator 至 vee-validate 中
import validators from '../utils/validators'
import each from 'lodash/each'
each(validators, (validator, key) => {
  Validator.extend(key, validator)
})

// global config
const config = {
  // 初始语系 (tw / en)
  locale: localeService.getCurrentLang(),
  // 触发检核的时机 (输入or离开输入框)
  events: 'input|blur',
  // 默认各语系检核失败提示文字 (需要对应 locale 设定)
  dictionary: { tw, en }
}

Vue.use(VeeValidate, config)

// 将自定义的语系文字配置文件 合并至 默认检核逻辑的语系文字
Validator.localize({tw: twVeeValidateDic, en: enVeeValidateDic})

检核文字配置文件

/* tw/veeValidateDic.js */

import attributes from './lang'

export default {
  custom: {
    // 当有相同 validator 要呈现不同文字时,可以在这边做例外的设定
    __userId: {
      required: field => `务必填写${field}(此资讯将作为登入账号使用)`
    }
  },
  messages: {
    // 覆写原有 validator 错误消息
    required: (field) => `请输入${field}`,
    min: (field, args) => `${field}长度需超过${args[0]}符!`,
    // 设定自订 validator 错误消息
    requireAlphaNum: (field) => `${field}需包含英文与数字`,
    noSameAsCustId: (field) => `${field}不可包含身份证号`
  },
  // 对应到输入元素的name名称  or 
  // 在上方 error message 的 field 就会自动取代成"中文"语系档中 key 为 __userId 的文字
  attributes
}

参考资讯


VeeValidate 官方网站

Custom Validator - Not Running on Blank Value


希望此篇文章可以帮助到需要的人

若内容有误或有其他建议请不吝留言给笔者喔 !