Vue2.x+Element UI 密码规则组件封装

一 效果图

实现效果图

二 组件封装步骤

1 组件目录结构如下
在这里插入图片描述

2 组件是需要一个密码输入框和一个pop 框,在input获取焦点的时候展示popover,popover 中的每一项都要根据输入内容实时匹配是否满足某一规则,因此里面的内容需要动态渲染,如下结构

<template>
  <el-popover popper-class="gt-password-popper" v-bind="popoverOptions">
    <template slot="reference">
      <el-input
        class="gt-password"
        v-bind="$attrs"
        v-on="$listeners"
        :type="passwordType ? 'password' : 'text'"
      >
        <i
          @click="triggerPassword"
          slot="suffix"
          :class="['trigger-password', passwordType ? '' : 'show']"
        ></i>
      </el-input>
    </template>
    <CheckList
      :pagek="pagek"
      :values.sync="checkValuesObj"
      :title="$t('Basic.Password_must_include')"
      :options="checkList"
    />
  </el-popover>
</template>


<script lang="ts">
import { Component, Prop, Vue, Watch, Computed } from "vue-property-decorator";
import CheckList from "./CheckList.vue";
import { ruleItem } from "./index.type";

const popDefault = {
  placement: "right-start",
  width: 228,
  trigger: "focus"
};

@Component({
  components: { CheckList }
})
export default class Password extends Vue {
  pagek = new Date().getTime();
  @Prop({
    type: Array as PropType<ruleItem[]>,
    required: true
  })
  checkList;
  @Prop({
    type: Array as PropType<ruleItem[]>,
    required: false
  })
  popover;
  private popoverOptions = {};
  checkValuesObj: any = {};
  passwordType: Boolean = true;

  created() {
    this.popoverOptions = { ...this.popover, ...popDefault };
    this.update(this.checkList);
  }

  update = (validList: string[] = [], clear: Boolean = false) => {
    // 每次update 前先清空 checkValuesObj
    Object.keys(this.checkValuesObj).forEach(v => {
      this.$set(this.checkValuesObj, v, "");
    });

    let tempObj = {};
    this.$nextTick(() => {
      this.pagek = new Date().getTime();
      tempObj =
        validList.length > 0 &&
        validList.reduce(
          (res = {}, key = "1") => Object.assign(res, { [key]: "success" }),
          {}
        );

      if (tempObj) {
        Object.keys(tempObj).forEach(v => {
          this.$set(this.checkValuesObj, v, tempObj[v]);
        });
      }

      if (clear) {
        Object.keys(this.checkValuesObj).forEach(v => {
          this.$set(this.checkValuesObj, v, "");
        });
      }
    });
  };

  triggerPassword() {
    this.passwordType = !this.passwordType;
  }
}
</script>

<style lang="scss" scoped>
.trigger-password {
  display: block;
  width: 24px;
  height: 100%;
  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADmElEQVRoQ+2Y14vXUBCFv7X3Dgp2sIEVy4MP/vUKPth7x4q99658MIFLTDbJTRZ3IQNB/CV37pkzZ2bu3QlmuE3McPyMAfzvDI4ZGDPQk4FRQj0J7L18zEBvCns6mI4Z2AC8AL61iW26BbAV2Ax8Bk4Dv5uCGDKA2cCqeBYC84F5AeB7MPoFeB3PrxK4Arw/XwWeN4H3/RABCNq0rwBmtdk0mL0NPI7vs8D3DWB1pHtZAvo98BL4GIyr4z+RCTOyHFgZ767Hu2zwuQEIZGdIRR8/gIfAU0CpdLEUvLq/CHzt4qCrhNYB24A5wE/gfsgg1bPvNgJmyFowA4IyM49inRhT8PpynX7uJNJqjKVtAGpb1teGx1fAjQrGlceuKODy5hbltQrZ+NsbYAewJhY9AW4N1YVkZk8UqQzpWLmUTfD748d3IasP8f9FwNsa8M8SRxIkURJmUFeSjFVmoykD6n0vsCTYvgQUoFKHBnkkmH8A3A2w5U1T2dyskYpNwT3nAp8A96yti8kCkDUZNYimAiuAyfz5GvBbAJ/C7gE+VWbtGIQYbAz6FMM/VhfAAuBgtD9boyzYbepM9hcDl6NYy9+l4JWfzcBW67StMzOwO6RrOz5XlYm6AA4BSwEZtbWVp2Z502OAk/hkxRkmBW/BWgtHQ9snJgnAV/rcF/ND6Z4pfz/VAZTBW7BK0gAk5fhUBdBVQoej0C9E9xBXFXh/t1Xa1dpIyDqwqK2Ds10k5EZdiliwfl/0+TrwZvxASMIhaLeqMglUOvpU/xLTqYgLp+U2aj3IXNmKKerUTVulARV9XvC+2xSgTtX0eGtP5j3JupcNpPZu0DQHBCo4u4GDarJB5rcpeLtNwbCgPF54mNNSqaVk2J22R/EOMsgK51VHCQdRykwKvq42/d5TqOBSk20nsOcnzax5VBn8QpMy5FneIMoF61HDHm6xqmWz7EXG85OnVg9uhdkm14esigOi94Sqo0olKW0kVF4oKAOxCCfTfF0W/F3G9aGsDFbzpibrre7ChfOcAFJgqWwMxsGnPPxXILY/97AZ+HimMjPlS5BkmKHO1ieAtFU6XQXV5UrpGu8HMp9tuQFU9fm+l/qsIHICqBtSWQD6LuoagH998EqppUOqL47s9V0DsBC9I3iOb/V3m2xkLRd2DUC3FmrjgGm5f+/PcgLovemQDsYAhmQzx9eYgRzWhlwzZmBINnN8jRnIYW3INX8BE5PmMWUaunsAAAAASUVORK5CYII=")
    no-repeat center;
  background-size: 100%;
  cursor: pointer;
  &.show {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAC80lEQVRoQ+2XV4sVQRCFvzXnDIoYEExgVnzwwV/vgw8GjGBEzArmnJVvqYFhdubO9E4vy0oXXO6yt7u6zqlT1dVTLHCbWuDxUwDMdwZLBkoGRjJQJDSSwNHbSwZGUzjSQcnASAJHb8+ZgcXApvisBJYDyyLCH8B34CvwBngL/BodPWSZhQx6B7ABWDQwqL8B4nF8D9w2c9mYDGwGdgPram4/AK+AT8G4rGtmwoysBdy3vrbnI/Aw9iUDmQ0AAzkQUvHAn4BMvgCUyhBbCmyPzPm39hq4E8CH+JhekwpgG7AXWBIafgQ8BX7XTvS3ncG0taCpfQMUaF371o1AzGTl8z7wfCiCoQDUtqxvrbF1u4XxjcDBkEtbDErqVovuzer+AO0+AdwF/vQBGQJAZg5Hkcq0jpVL0wz+WPzzfbCtvjW1b1Yq7V/tKF4JkigJs1Pd7OtWfQBk5giwJti+FgXaDF6Qp4N5ZfUAsNPUzbP2ALtC5xc6grMpeKa18Rm4DnzrysQkAKuCUUF8AQy+y5GBqWOZv9ISfHW+5x2PTNh5BNpm1o4gjMHGoE9jmGFdAFYAJ6P92RplwW7TZbK/GrgxoB1uCUnaai9O8GkGDoV0BXG5jcAuAKdCtzIq8/Uu03bmWcCOcn5AGzSjZ8LnuZ4i1efRyJj1dKm5/r8FoIRORFHOp4SsA4s6WUJmaj6LWAKVjjF4d9h2k4q4klqzjVrMVW+vyzFnG/XOkHnnJwvdM6uZanAXagZnN/CimuuLzFFlXzSELBdZBWSuRwnZ9gZ2UtVeAo4qWUaJejaaw5yX0bOOYc5+r5a1lGHuXseo0tpx+0aJtk05xmkZlwzno2qc9qUm6516T7mJe+6X6Z8nPWgs9OoZ6drqeWmBmpnmI8hMOm4n22wy0DxkNk9Ktf0OeBJv5OTAqw05AFS+Uh71yqVvPBkEKieAQQfmXlQA5GY01V/JQCpjudeXDORmNNVfyUAqY7nXlwzkZjTVX8lAKmO51/8DYoDEMYv3QPQAAAAASUVORK5CYII=");
  }
}
</style>

CheckList 组件内容如下

<template>
  <div class="check-list" :key="pagek">
    <div class="check-list__title">{{ title }}</div>
    <div v-for="{ key, label } in options" :key="key">
      <div class="check-list-item">
        <span class="check-list-item__prefix">
          <i
            v-if="isEqual(key, 'success')"
            class="iconfont el-icon-success"
          ></i>
          <i
            v-else-if="isEqual(key, 'error')"
            class="iconfont el-icon-error"
          ></i>
          <i
            v-else-if="isEqual(key, 'warning')"
            class="iconfont el-icon-warning"
          ></i>
          <i v-else-if="isEqual(key, 'info')" class="el-icon-info"></i>
          <i v-else class="iconfont el-icon-pending"></i>
        </span>
        <div class="check-list-label">{{ label }}</div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch, Computed } from "vue-property-decorator";

interface option {
  key: string | number | symbol;
  label: string;
}

@Component({})
export default class CheckList extends Vue {
  status = ["pending", "success", "error", "warning", "info"];

  @Prop({
    default: new Date().getTime()
  })
  pagek;
  @Prop({
    type: Object,
    default: () => ({})
  })
  values;

  @Prop({
    type: Array as PropType<option[]>,
    default: () => []
  })
  options;

  @Prop({
    type: String,
    default: ""
  })
  title;

  get defaultStatus() {
    return this.options.reduce(
      (res, { key }) => Object.assign(res, { [key]: status[0] }),
      {}
    );
  }

  get statusMap() {
    return Object.assign({}, this.defaultStatus, this.values);
  }

  isEqual = (
    key: string | number | symbol,
    statusCode: string | number
  ): boolean => {
    return this.statusMap[key] == statusCode;
  };
}
</script>

<style lang="scss" scoped>
// 胶囊状态颜色
$--color--capsule--success: #40b828;
$--color--capsule--danger: #f0553a;
$--color--capsule--cancel: #878e99;
$--color--capsule--warning: #fc971c;
$--color--capsule--processing: #1a9bf6;
$--color--capsule--info: #878e99;
.el-icon-success {
  color: $--color--capsule--success;
}
.el-icon-error {
  color: $--color--capsule--danger;
}
.el-icon-warning {
  color: $--color--capsule--warning;
}
.el-icon-info {
  color: $--color--capsule--info;
}
.el-icon-pending {
  &::before {
    content: "";
    display: inline-flex;
    width: 12px;
    height: 12px;
    border: 2px solid rgba(#000, 0.15);
    border-radius: 50%;
    // vertical-align: baseline;
    // margin-bottom: -2px;
  }
}

.check-list {
  font-size: 13px;
}
.check-list__title {
  line-height: 20px;
  color: $--color-text-lighter;
  margin-bottom: 12px;
}
.check-list-item {
  line-height: 20px;
  margin-bottom: 8px;
}
.check-list-item__prefix {
  float: left;
  font-size: 16px;
}
.check-list-label {
  margin-left: 16px + 12px;
}
</style>

由于我们项目使用了 ts ,因此会有一些必要的类型校验
index.type.ts 内容如下

export interface rule {
  key: string;
  label: string;
}

export interface ruleItem extends rule {
  regExp?: RegExp;
  validator?: (value: string) => boolean;
}

utils.ts 中为校验规则,以及校验结果

import { ruleItem } from "./index.type";
import i18nInstance from "@root/i18n";

export const usePassword = (options?: Array<ruleItem | string>) => {
  const defaultRules: ruleItem[] = [
    {
      key: "length",
      label: i18nInstance.t("Basic.At_least_8_characters"),
      regExp: /^.{8,}$/
    },
    {
      key: "lowercase",
      label: i18nInstance.t("Basic.Use_lowercase_letters"),
      regExp: /[a-z]+/
    },
    {
      key: "capital",
      label: i18nInstance.t("Basic.Use_capital_letters"),
      regExp: /[A-Z]+/
    },
    {
      key: "number",
      label: i18nInstance.t("Basic.Use_numbers"),
      regExp: /\d{1,}/
    },
    {
      key: "special",
      label: i18nInstance.t("Basic.Use_special_characters"),
      regExp: /[!@#$%^&*? ]{1,}/
    }
  ];

  const ruleItems: ruleItem[] = options
    ? options.reduce((res, item) => {
        if (typeof item === "string") {
          const rule = defaultRules.find(i => item === i.key);
          if (rule) {
            res.push(rule);
          } else {
            throw new Error("invalid ruleKey");
          }
        } else {
          res.push(item);
        }
        return res;
      }, [] as ruleItem[])
    : [...defaultRules];

    // 返回默认的匹配规则map 和 校验结果mapList
  return {
    checkList: ruleItems.map(({ key, label }) => ({ key, label })),
    validate: (value: string): { validList: string[]; valid: boolean } => {
      // 过滤出符合正则的rule[], map返回key[]
      const validList = ruleItems
        .filter(({ regExp, validator }) => {
          if (validator) {
            return validator(value);
          } else if (regExp) {
            return regExp.test(value);
          } else {
            return false;
          }
        })
        .map(({ key }) => {
          return key;
        });
      return {
        validList,
        valid: validList.length === ruleItems.length
      };
    }
  };
};

为了方便使用容易将逻辑与组件放入index.ts

import Password from "./src/index.vue";
import { usePassword } from "./src/utils";

export { usePassword };

export default Password;

三 使用组件使用组件
import Password, { usePassword } from "@/components/page/Password/index"; //引入组件

passwordE = null; // password 组件元素相关校验逻辑

...
created() { //组件生命周期内使用组件
   this.passwordE = usePassword();
   this.checkList = this.passwordE.checkList;
 }
...

// 密码规则自定义校验器
get rules(){
let _ = this;
return {
 password: {
        validator: (
          rule: any,
          value: string,
          callback: (v?: Error) => void
        ) => {
          const { validList = [], valid } = _.passwordE.validate(value);
          (_.$refs.passwordRef as any).update(validList);

          // 几条规则是否都以校验完成,否则会有无效密码提示
          if (!valid) {
            callback(new Error(_.$t("Basic.invalid_password") as string));
          } else {
            callback();
          }
        },
        trigger: ["change", "blur"]
      },
   }
}
 


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值