Skip to content

表单组件字段校验

日常开发在使用表单组件的时候,应该根据实际的业务场景,对字段是否必填、字段长度,字段所包含的字符类型做一些校验,同时在调用接口的时候,相关数据接口需要对请求的数据进行字段验证

代码示例:

vue
<!-- 修改密码弹出框 -->
<template>
  <!-- 容器 -->
  <el-dialog :visible.sync="visible" width="500px" v-draggable :close-on-click-modal="false" :modal-append-to-body="false" @close="onClose">
    <template v-if="visible">
      <span slot="title">
        <i class="fa fa-pencil-square-o b"> 修改密码</i>
      </span>
      <!-- 表单信息 -->
      <el-form ref="form" :model="form" :rules="formRules" label-width="120px" size="small">
        <el-form-item label="原密码:" prop="oldPassword">
          <el-input type="password" :show-password="true" v-model.trim="form.oldPassword" :maxlength="20" placeholder="输入原密码"></el-input>
        </el-form-item>
        <el-form-item label="新密码:" prop="newPassword">
          <el-input type="password" :show-password="true" v-model.trim="form.newPassword" :maxlength="20" placeholder="输入新密码"></el-input>
          <!-- 密码级别 -->
          <v-password-level :value="form.newPassword" />
        </el-form-item>
        <el-form-item label="确认新密码:" prop="confirmPassword">
          <el-input type="password" :show-password="true" v-model.trim="form.confirmPassword" :maxlength="20" placeholder="再次输入新密码"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="onCancel" size="small">取消</el-button>
        <el-button type="primary" @click="onSave" size="small">保存</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script>
const jsBase64 = () =>
  import(/* webpackChunkName: 'async_vendors/js-base64' */ 'js-base64');
import blueimpMd5 from 'blueimp-md5';
import store from '@/store/index';
import { GET_APP_INFO } from '@/store/system';
import { transPassword } from '@/mixin/page';
import fetch from '@/config/fetch';
import validate from '@/assets/js/validate';

/**
 * 校验密码
 *  密码强度级别
 *    '1': 纯数字(6-20位)
 *    '2': 字母和数字必需同时存在(6-20位)
 *    '3': 至少为1位,只要不为空即可
 *    '4': 大小写字母、数字、特殊字符必需同时存在(8-20位)
 *    '5': 大小写字母、数字、特殊字符必需同时存在(8-20位),不能包含3个及以上相同或字典连续字符、不能包含3个及以上键盘连续字符
 * @export
 * @param {*} rule
 * @param {*} value
 * @param {*} callback
 */
function validatePassword(rule, value, callback) {
  let level = store.getters[GET_APP_INFO].pwdLevel || '1';
  switch (level) {
    case '1':
      if (validate.validateNumberPassword(value)) {
        return callback();
      } else {
        return callback(new Error('密码应为6 - 20位数字'));
      }
    case '2':
      if (validate.validatePassword(value)) {
        return callback();
      } else {
        return callback(new Error('密码应为6 - 20位字符,含有字母与数字'));
      }
    case '3':
      if (util.isNotEmpty(value)) {
        return callback();
      } else {
        return callback(new Error('密码至少为1位'));
      }
    case '4':
      if (validate.validateStrongPassword(value)) {
        return callback();
      } else {
        return callback(
          new Error('密码应为8 - 20位字符,含有大小写字母、数字、特殊字符')
        );
      }
    case '5':
      /**
       * 是否包含3个及以上相同或字典连续字符
       * @param {*} value
       * @returns
       */
      function hasContinuousChar(value) {
        value = value || '';
        if (value === '') {
          return false;
        }
        value = String(value);
        for (let i = 0; i < value.length - 2; i++) {
          // 转ASCII
          let n1 = value[i].charCodeAt();
          let n2 = value[i + 1].charCodeAt();
          let n3 = value[i + 2].charCodeAt();
          // 判断重复字符
          if (n1 == n2 && n1 == n3) {
            return true;
          }
          // 判断连续字符: 正序 + 倒序
          if (
            (n1 + 1 == n2 && n1 + 2 == n3) ||
            (n1 - 1 == n2 && n1 - 2 == n3)
          ) {
            return true;
          }
        }
        return false;
      }
      /**
       * 是否包含3个及以上键盘连续字符
       * @param {*} value
       * @returns
       */
      function hasKeyBoardContinuousChar(value) {
        value = value || '';
        if (value === '') {
          return false;
        }
        value = String(value);
        /**
         * 键盘字符表(小写)
         */
        const normalKeyboard = [
          ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\0'],
          ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
          ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '\0', '\0'],
          ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0'],
        ];
        /**
         * shift键盘的字符表
         */
        const shiftKeyboard = [
          ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\0'],
          ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '|'],
          ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ':', '"', '\0', '\0'],
          ['z', 'x', 'c', 'v', 'b', 'n', 'm', '<', '>', '?', '\0', '\0', '\0'],
        ];

        value = value.toLowerCase().replace(/[\s\uFEFF\xa0\u3000]/g, '');

        // 获取字符串长度
        let nStrLen = value.length;

        // 定义位置数组:row - 行,col - column 列
        let pRowCharPos = [];
        let pColCharPos = [];

        for (let i = 0; i < nStrLen; i++) {
          let chLower = value[i];
          pColCharPos[i] = -1; //-1为~/`键
          // 检索在表normalKeyboard中的位置,构建位置数组
          for (let j = 0; j < 4; j++) {
            for (let k = 0; k < 13; k++) {
              if (chLower == normalKeyboard[j][k]) {
                pRowCharPos[i] = j;
                pColCharPos[i] = k;
              }
            }
          }
          // 在表normalKeyboard中没找到,到表shiftKeyboard中去找,找到则continue
          if (pColCharPos[i] >= 0) {
            continue;
          }
          // 检索在表shiftKeyboard中的位置,构建位置数组
          for (let j = 0; j < 4; j++) {
            for (let k = 0; k < 13; k++) {
              if (chLower == shiftKeyboard[j][k]) {
                pRowCharPos[i] = j;
                pColCharPos[i] = k;
              }
            }
          }
        }
        // 匹配坐标连线
        for (let j = 1; j <= nStrLen - 2; j++) {
          //同一行
          if (
            pRowCharPos[j - 1] == pRowCharPos[j] &&
            pRowCharPos[j] == pRowCharPos[j + 1]
          ) {
            // 键盘行正向连续(asd)或者键盘行反向连续(dsa)
            if (
              (pColCharPos[j - 1] + 1 == pColCharPos[j] &&
                pColCharPos[j] + 1 == pColCharPos[j + 1]) ||
              (pColCharPos[j + 1] + 1 == pColCharPos[j] &&
                pColCharPos[j] + 1 == pColCharPos[j - 1])
            ) {
              return true;
            }
          }
          //同一列
          if (
            pColCharPos[j - 1] == pColCharPos[j] &&
            pColCharPos[j] == pColCharPos[j + 1]
          ) {
            //键盘列连续(qaz)或者键盘列反向连续(zaq)
            if (
              (pRowCharPos[j - 1] + 1 == pRowCharPos[j] &&
                pRowCharPos[j] + 1 == pRowCharPos[j + 1]) ||
              (pRowCharPos[j - 1] - 1 == pRowCharPos[j] &&
                pRowCharPos[j] - 1 == pRowCharPos[j + 1])
            ) {
              return true;
            }
          }
        }
        return false;
      }
      if (validate.validateStrongPassword(value)) {
        if (!hasContinuousChar(value)) {
          if (!hasKeyBoardContinuousChar(value)) {
            return callback();
          } else {
            return callback(new Error('密码不能包含3个及以上键盘连续字符'));
          }
        } else {
          return callback(new Error('密码不能包含3个及以上相同或字典连续字符'));
        }
      } else {
        return callback(
          new Error('密码应为8 - 20位字符,含有大小写字母、数字、特殊字符')
        );
      }
    default:
      return callback();
  }
}

export default {
  data() {
    const validatorConfirmPassword = (rule, value, callback) => {
      if (this.form.confirmPassword === this.form.newPassword) {
        callback();
      } else {
        callback(new Error('两次密码输入不一致'));
      }
    };
    return {
      visible: false, //显示隐藏状态
      form: {
        oldPassword: '',
        newPassword: '',
        confirmPassword: '',
      },  //表单对象
      formRules: {
        oldPassword: [
          { required: true, message: '请输入原密码', trigger: 'blur' },
        ],
        newPassword: [
          { required: true, message: '请输入新密码', trigger: 'blur' },
          { validator: validatePassword, trigger: 'blur' },
        ],
        confirmPassword: [
          { required: true, message: '请再次输入新密码', trigger: 'blur' },
          { validator: validatorConfirmPassword, trigger: 'blur' },
        ],
      },  //表单规则
    };
  },
  methods: {
    /**
     * 打开修改密码弹出框
     */
    open() {
      this.visible = true;
    },
    /**
     * 构建参数
     */
    async buildParams() {
      try {
        let { Base64 } = await jsBase64();
        return {
          opassword: transPassword(blueimpMd5(this.form.oldPassword)),
          password: transPassword(blueimpMd5(this.form.newPassword)),
          basePwd: Base64.encode(this.form.newPassword),
        };
      } catch (err) {
        return Promise.reject(err);
      }
    },
    /**
     * 校验表单
     * 1、TODO:调用el-form表单校验方法,对表单进行校验,包括必填校验、字段长度校验、字段是否特殊字符等
     */
    validateForm() {
      return new Promise((resolve) => {
        this.$refs.form.validate((valid) => {
          return resolve(valid);
        });
      });
    },
    /**
     * 保存数据
     * 1、TODO:保存数据的时候,相关数据接口需要对请求的数据进行字段验证
     */
    async save() {
      try {
        let params = await this.buildParams();
        await fetch.post('/pa/user/modifyPassword', params);
      } catch(err) {
        return Promise.reject(err);
      }
    },
    /**
     * 处理保存
     * 1、校验表单
     * 2、保存数据
     */
    async onSave() {
      try {
        this.$loading();
        let valid = await this.validateForm();
        if (valid) {
          await this.save();
        }
        this.$message({
          type: 'success',
          message: '保存成功!',
        });
        this.visible = false;
      } catch (err) {
        console.error(err);
        this.$message({
          type: 'error',
          message: err.msg,
        });
      } finally {
        this.$loadingClose();
      }
    },
    /**
     * 处理取消
     */
    onCancel() {
      this.visible = false;
    },
    /**
     * 处理弹出框关闭
     */
    onClose() {
      this.$refs.form.resetFields();
    },
  },
};
</script>