|
@@ -0,0 +1,233 @@
|
|
|
+<template>
|
|
|
+ <el-select
|
|
|
+ ref="select"
|
|
|
+ :value="value"
|
|
|
+ placeholder="请选择"
|
|
|
+ :size="size"
|
|
|
+ clearable
|
|
|
+ :disabled="disabled"
|
|
|
+ :filterable="filterable"
|
|
|
+ :filter-method="filterMethod"
|
|
|
+ style="width: 100%;"
|
|
|
+ @clear="clear"
|
|
|
+ @visible-change="visibleChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ ref="option"
|
|
|
+ class="tree-select__option"
|
|
|
+ :value="optionData.id"
|
|
|
+ :label="optionData.name"
|
|
|
+ >
|
|
|
+ <el-tree
|
|
|
+ ref="tree"
|
|
|
+ class="tree-select__tree"
|
|
|
+ :class="`tree-select__tree--${multiple ? 'checked' : 'radio'}`"
|
|
|
+ :node-key="nodeKey"
|
|
|
+ :data="data"
|
|
|
+ :props="props"
|
|
|
+ :default-expanded-keys="[value]"
|
|
|
+ :show-checkbox="multiple"
|
|
|
+ :highlight-current="!multiple"
|
|
|
+ :expand-on-click-node="multiple"
|
|
|
+ :filter-node-method="filterNode"
|
|
|
+ @node-click="handleNodeClick"
|
|
|
+ @check-change="handleCheckChange"
|
|
|
+ ></el-tree>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'TreeSelect',
|
|
|
+ props: {
|
|
|
+ // v-model绑定
|
|
|
+ value: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ // 节点是否可被选择 多选还是单选
|
|
|
+ multiple: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // 树形的数据
|
|
|
+ data: {
|
|
|
+ type: Array,
|
|
|
+ default: function () {
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 每个树节点用来作为唯一标识的属性
|
|
|
+ nodeKey: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: 'id'
|
|
|
+ },
|
|
|
+ // 组件大小
|
|
|
+ size:{
|
|
|
+ type: String,
|
|
|
+ default: 'small'
|
|
|
+ },
|
|
|
+ // 是否可搜索
|
|
|
+ filterable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // 是否禁用
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ // tree的props配置
|
|
|
+ props: {
|
|
|
+ type: Object,
|
|
|
+ default: function () {
|
|
|
+ return {
|
|
|
+ label: 'label',
|
|
|
+ children: 'children'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ optionData: {
|
|
|
+ id: '',
|
|
|
+ name: ''
|
|
|
+ },
|
|
|
+ filterFlag: false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ value: {
|
|
|
+ // 选择的数据发生变化触发
|
|
|
+ handler(val) {
|
|
|
+ if (!this.isEmpty(this.data)) {
|
|
|
+ this.init(val)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ },
|
|
|
+ data: function (val) {
|
|
|
+ if (!this.isEmpty(val)) {
|
|
|
+ this.init(this.value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {},
|
|
|
+ methods: {
|
|
|
+ // 是否为空
|
|
|
+ isEmpty(val) {
|
|
|
+ for (let key in val) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ },
|
|
|
+ // 节点被点击时的回调
|
|
|
+ handleNodeClick(data) {
|
|
|
+ if (this.multiple) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$emit('input', data[this.nodeKey])
|
|
|
+ this.$refs.select.visible = false
|
|
|
+ // 当点击选中树的节点时,下拉框不会自动隐藏,感觉体验不太好。查看文档,
|
|
|
+ // select组件也没有控制下拉框显示隐藏的属性,然后在select组件源码中找到了visible属性,用来控制下拉框显示隐藏。
|
|
|
+ // 点击选中树的节点时设置 this.$refs.select.visible = false 即可。
|
|
|
+ },
|
|
|
+ // 节点选中状态发生变化时的回调
|
|
|
+ handleCheckChange() {
|
|
|
+ const nodes = this.$refs.tree.getCheckedNodes()
|
|
|
+ const value = nodes.map((item) => item[this.nodeKey]).join(',')
|
|
|
+ this.$emit('input', value)
|
|
|
+ },
|
|
|
+ init(val) {
|
|
|
+ // 多选
|
|
|
+ if (this.multiple) {
|
|
|
+ const arr = val.toString().split(',')
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // setCurrentKey() 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
|
|
|
+ this.$refs.tree.setCheckedKeys(arr)
|
|
|
+ // getCheckedNodes() 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
|
|
|
+ const nodes = this.$refs.tree.getCheckedNodes()
|
|
|
+ this.optionData.id = val
|
|
|
+ this.optionData.name = nodes
|
|
|
+ .map((item) => item[this.props.label])
|
|
|
+ .join(',')
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 单选
|
|
|
+ else {
|
|
|
+ val = val === '' ? null : val
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // setCurrentKey() 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
|
|
|
+ this.$refs.tree.setCurrentKey(val)
|
|
|
+ if (val === null) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // getNode() 根据 data 或者 key 拿到 Tree 组件中的 node
|
|
|
+ const node = this.$refs.tree.getNode(val)
|
|
|
+ this.optionData.id = val
|
|
|
+ this.optionData.name = node.label
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 下拉框出现/隐藏时触发
|
|
|
+ visibleChange(e) {
|
|
|
+ if (e) {
|
|
|
+ const tree = this.$refs.tree
|
|
|
+ this.filterFlag && tree.filter('')
|
|
|
+ this.filterFlag = false
|
|
|
+ let selectDom = null
|
|
|
+ if(this.multiple) {
|
|
|
+ // $el.querySelector() == document.querySelector() // 获取 dom 元素
|
|
|
+ selectDom = tree.$el.querySelector('.el-tree-node.is-checked')
|
|
|
+ } else {
|
|
|
+ selectDom = tree.$el.querySelector('.is-current')
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ this.$refs.select.scrollToOption({ $el: selectDom })
|
|
|
+ }, 0)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 下拉框点击
|
|
|
+ clear() {
|
|
|
+ this.$emit('input', '')
|
|
|
+ },
|
|
|
+ // 自定义搜索方法
|
|
|
+ filterMethod(val) {
|
|
|
+ this.filterFlag = true
|
|
|
+ this.$refs.tree.filter(val)
|
|
|
+ },
|
|
|
+ // 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
|
|
|
+ filterNode(value, data) {
|
|
|
+ if (!value) return true
|
|
|
+ const label = this.props.label || 'name'
|
|
|
+ return data[label].indexOf(value) !== -1
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+// 得用 ::v-deep
|
|
|
+.tree-select__option {
|
|
|
+ &.el-select-dropdown__item {
|
|
|
+ height: auto;
|
|
|
+ line-height: 1;
|
|
|
+ padding: 0;
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+ // 得用 ::v-deep
|
|
|
+.tree-select__tree {
|
|
|
+ padding: 4px 20px;
|
|
|
+ font-weight: 400;
|
|
|
+ &.tree-select__tree--radio {
|
|
|
+ .el-tree-node.is-current > .el-tree-node__content {
|
|
|
+ //选中字体的样式
|
|
|
+ color:red;
|
|
|
+ font-weight: 700;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|