vue-treeselect 校验失败添加红框

发布时间 2023-08-16 17:43:10作者: shayloyuki

需求

vue-treeselect 校验及清除校验 这篇介绍了用 @input 在校验失败时显示校验信息。但还需要同时显示红色边框。如下图所示:

image

解决办法

思路:动态绑定类名,校验失败时切换类名,更改边框颜色。

子组件 SelectTree

二次封装 vue-treeselect:组件 SelectTree
<template>
  <div class="select-tree">
    <treeselect
      :class="{ error: isError === true }"
      appendToBody
      z-index="9000"
      :flat="isFlat"
      :multiple="isMultiple"
      :noOptionsText="noOptionsText"
      noResultsText="未搜索到匹配项"
      :value-consists-of="valueConsistsOf"
      :limit="limitNum"
      :limitText="(count) => `+${count}`"
      v-model="modelValue"
      @input="performValidate"
      :options="optionTree"
      :show-count="showCount"
      :normalizer="normalizerNode"
      :placeholder="placeText ? placeText : `请选择上级${nodeLabel}(可搜索)`"
      :disabled="isDisabled"
      :disable-branch-nodes="disBranchNodes"
      :clearable="showClearable"
    >
      <label
        slot="option-label"
        slot-scope="{ node, shouldShowCount, labelClassName, countClassName }"
        :class="labelClassName"
      >
        <!-- 可自定义选项内容 -->
        <slot
          name="diy-option"
          v-bind="{ node, shouldShowCount, countClassName }"
        >
          {{ node.label }}
          <span v-if="shouldShowCount" :class="countClassName"
            >({{ node.children.length }})</span
          >
        </slot>
      </label>

      <div slot="value-label" slot-scope="{ node }">
        <!-- 可自定义值标签 -->
        <slot name="diy-value" v-bind="{ node }">
          {{ node.label }}
        </slot>
      </div>
    </treeselect>
  </div>
</template>

<script>
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";

export default {
  name: "SelectTree",
  components: {
    Treeselect,
  },
  computed: {
    modelValue: {
      get() {
        // 若选项中没有为 0 的 id,则自动把 modelValue 置为 null
        const flag = this.optionTree.find(
          (ele) => ele[this.exchangeNode.id] === 0
        );
        if (!flag && this.modelVal === 0) {
          return null;
        } else {
          return this.modelVal;
        }
      },
      set(val) {
        this.$emit("return-model", val);
        return val;
      },
    },
  },
  props: {
    // 校验失败:默认成功
    isError: {
      type: Boolean,
      default: false,
    },
    appendToBody: {
      type: Boolean,
      default: false,
    },
    // 限制所选选项的显示
    limitNum: {
      type: Number,
      default: 5,
    },
    // 防止价值组合:对于非固定和多选模式,如果选中了分支节点及其所有后代,则vue-treeselect会将它们组合到值数组中的单个项目中
    valueConsistsOf: {
      type: String,
      default: "BRANCH_PRIORITY",
    },
    // 是否开启多选
    isMultiple: {
      type: Boolean,
      default: false,
    },
    // 是否开启平面模式(父子节点不关联)
    isFlat: {
      type: Boolean,
      default: false,
    },
    // 无选项内容时的提示文本
    noOptionsText: {
      type: String,
      default: "暂无数据",
    },
    // 下拉框绑定值
    modelVal: {
      type: [String, Number, Array, null],
      default: null,
    },
    // 是否显示清空按钮
    showClearable: {
      type: Boolean,
      default: true,
    },
    // 自定义提示
    placeText: {
      type: [String, null],
      default: null,
    },
    // 是否禁止选择有子节点的选项
    disBranchNodes: {
      type: Boolean,
      default: false,
    },
    // 节点名称
    nodeLabel: {
      type: String,
      default: "节点",
    },
    // 是否显示计数
    showCount: {
      type: Boolean,
      default: true,
    },
    // 转换节点结构
    exchangeNode: {
      type: Object,
      default: () => {
        return {
          id: "id",
          label: "label",
          children: "children",
        };
      },
    },
    // 是否禁用
    isDisabled: {
      type: Boolean,
      default: false,
    },
    // 选项树:用于上级节点选项中选择
    optionTree: {
      type: Array,
      default: () => {
        let tree = [
          {
            id: 0,
            label: "全部节点",
            children: [],
          },
        ];
        tree[0].children = [];
        return tree;
      },
    },
  },
  methods: {
    // 进行校验
    performValidate() {
      this.$emit("perform-validate");
    },
    /* 转换节点数据结构 */
    normalizerNode(node) {
      if (node.children && !node.children.length) {
        delete node.children;
      }
      let res = {
        id: node[this.exchangeNode.id],
        label: node[this.exchangeNode.label],
        children: node[this.exchangeNode.children],
      };
      if (node[this.exchangeNode.isDisabled]) {
        res["isDisabled"] = node[this.exchangeNode.isDisabled];
      }
      return res;
    },
  },
};
</script>

<style lang="scss" scoped>
// 调整 vue-treeselect 禁用样式
::v-deep .vue-treeselect.vue-treeselect--disabled .vue-treeselect__control {
  background-color: #f5f7fa;
  border-color: #dfe4ed;
  cursor: not-allowed;

  .vue-treeselect__single-value {
    color: #c0c4cc;
  }
}

// 校验不通过时,输入框边框变红
::v-deep .vue-treeselect.error .vue-treeselect__control {
  border-color: #ff4949;
}
</style>

SelectTree 组件中,使用 :class="{ error: isError === true }" 动态绑定类名 error,设置有此类名时,边框变红:

// 校验不通过时,输入框边框变红
::v-deep .vue-treeselect.error .vue-treeselect__control {
  border-color: #ff4949;
}

父组件中使用

image

image

image

image

image

image

思考

以上这种传递变量更改动态类名的方式,虽然可以实现需求,但是有以下缺点:

  1. 组件耦合性太强,即使用时需要在多处书写代码;
  2. 若页面中有多处使用了子组件 SelectTree,某一处的校验会干扰其他处。

因此,此种方式不可取,需要进一步优化。

参考链接

  1. vue动态绑定类名的几种方法
  2. vue-treeselect 校验不出现红色方框的解决办法