index.vue 5.66 KB
<style>
.eagle-tree-search {
  padding-bottom: 10px;
}
.eagle-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0px 5px;
}
.eagle-tree-node-active {
  color: #1890ff;
}
.eagle-tree-tip {
  padding-bottom: 10px;
  border-bottom: 1px solid #e8e8e8;
}
.eagle-tree {
  padding-top: 10px;
}
.green {
  color: #52c41a;
}
.red {
  color: #f5222d;
}
</style>

<template>
  <div>
    <el-input v-model="showText" readonly :disabled="disabled" :size="size" :placeholder="placeholder" @focus="dialogVisible = true"></el-input>
    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      :close-on-click-modal="false"
      :append-to-body="true"
      width="30%"
    >
      <div v-if="filterable" class="eagle-tree-search">
        <el-input placeholder="输入关键字进行过滤" v-model="filterText" size="small"></el-input>
      </div>
      <div class="eagle-tree-tip">
        <span v-if="selected">已选中:<span class="red">{{ selected[treeProps.label] }}</span></span>
        <span v-else>请选择:</span>
      </div>
      <el-tree ref="tree" class="eagle-tree" :data="treeData" :props="treeProps" :node-key="nodeKey" @node-click="handleNodeClick" :filter-node-method="filterNode" :expand-on-click-node="false">
        <span class="eagle-tree-node" :class="{ 'eagle-tree-node-active': selected && selected[nodeKey] === data[nodeKey] }" slot-scope="{ node, data }">
          <span>{{ node.label }}</span>
          <span v-if="selected && selected[nodeKey] === data[nodeKey]">
            <i class="green el-icon-success"></i>
          </span>
        </span>
      </el-tree>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="handleConfirm">确 定</el-button>
        <el-button @click="dialogVisible = false">取 消</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
export default {
  name: 'TreeSelect',
  props: {
    // 组件值
    value: [Object, String, Number],
    // 弹出框标题
    title: {
      type: String,
      default: '选择'
    },
    // 输入框提示
    placeholder: {
      type: String,
      default: '请选择'
    },
    // 输入框禁用状态
    disabled: {
      type: Boolean,
      default: false
    },
    // 是否可搜索
    filterable: {
      type: Boolean,
      default: true
    },
    // 输入框大小
    size: String,
    // 格式化值类型 text object
    format: {
      type: String,
      default: 'text'
    },
    // 树组件值格式
    treeProps: {
      type: Object,
      default: () => {
        return { children: 'children', label: 'label' };
      }
    },
    // 树唯一标识
    nodeKey: {
      type: String,
      default: 'id'
    },
    // 是否每次打开弹出框刷新树数据
    uptodate: {
      type: Boolean,
      default: false
    },
    // 数据源
    dataSource: {
      type: [Promise, Function, Array],
      required: true,
    },
    // 是否动态数据源
    dynamicSource: {
      type: Boolean,
      default: false
    },
  },
  data() {
    return {
      // 弹出框显示状态
      dialogVisible: false,
      // 搜索文本
      filterText: undefined,
      // 树数据
      treeData: [],
      // 树数据列表
      treeDataList: [],
      // 选中值
      selected: undefined,
    };
  },
  watch: {
    // 输入搜索文本进行过滤
    filterText(val) {
      this.$refs.tree.filter(val);
    },
    // 显示弹出框刷新树数据
    dialogVisible(val) {
      if (val && this.uptodate) {
        this.queryTreeData();
      }
    },
    // 值为空时清空选中与搜索
    value(val) {
      if (!val) {
        this.selected = undefined;
        this.filterText = undefined;
      }
    },
    dataSource(val) {
      if (this.dynamicSource) {
        this.queryTreeData();
      }
    }
  },
  computed: {
    // 输入框显示值
    showText() {
      return this.format === 'text' ? this.textValue : !this.value ? undefined : this.value[this.treeProps.label];
    },
    // text模式输入框显示渲染
    textValue() {
      const find = this.treeDataList.find(data => data[this.nodeKey] === this.value);
      const obj = !find ? {} : find;
      return obj[this.treeProps.label];
    }
  },
  mounted() {
    this.queryTreeData();
  },
  methods: {
    // 过滤树数据
    filterNode(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    },
    // 查询树数据
    async queryTreeData() {
      if (this.dataSource instanceof Array) {
        this.treeData = this.dataSource;
      } else {
        this.treeData = await this.dataSource();
      }
      this.treeDataList = this.generateTreeList([...this.treeData]);
    },
    // 递归树数据
    generateTreeList(value) {
      const list = [];
      const generateChild = (child, result) => {
        return child.forEach(data => {
          result.push({ ...data, [this.treeProps.children]: undefined });
          if (data[this.treeProps.children]) {
            generateChild(data[this.treeProps.children], result);
          }
        });
      };
      generateChild(value, list);
      return list;
    },
    // 确定选择
    handleConfirm() {
      this.dialogVisible = false;
      if (this.format === 'text') {
        this.$emit('input', this.selected && this.nodeKey ? this.selected[this.nodeKey] : undefined);
      } else {
        this.$emit('input', this.selected);
      }
    },
    // 点击树节点选择
    handleNodeClick(data, node) {
      if (this.selected && this.nodeKey && this.selected[this.nodeKey] === data[this.nodeKey]) {
        this.selected = undefined;
      } else {
        this.selected = data;
      }
    }
  }
};
</script>