TreeSelect.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <template>
  2. <el-select
  3. ref="select"
  4. :value="value"
  5. :placeholder="placeholder"
  6. :size="size"
  7. :clearable="clearable"
  8. :disabled="disabled"
  9. :filterable="filterable"
  10. :filter-method="filterMethod"
  11. style="width: 100%;"
  12. @clear="clear"
  13. @focus="focus"
  14. @visible-change="visibleChange"
  15. >
  16. <el-option
  17. ref="option"
  18. class="tree-select__option"
  19. :value="optionData.id"
  20. :label="optionData.name"
  21. >
  22. <el-tree
  23. ref="tree"
  24. class="tree-select__tree"
  25. :class="`tree-select__tree--${multiple ? 'checked' : 'radio'}`"
  26. :node-key="nodeKey"
  27. :data="data"
  28. :props="props"
  29. :default-expanded-keys="[value]"
  30. :show-checkbox="multiple"
  31. :highlight-current="!multiple"
  32. :expand-on-click-node="multiple"
  33. :filter-node-method="filterNode"
  34. @node-click="handleNodeClick"
  35. @check-change="handleCheckChange"
  36. ></el-tree>
  37. </el-option>
  38. </el-select>
  39. </template>
  40. <script>
  41. export default {
  42. name: 'TreeSelect',
  43. props: {
  44. // v-model绑定
  45. value: {
  46. type: [String, Number],
  47. default: ''
  48. },
  49. // 节点是否可被选择 多选还是单选
  50. multiple: {
  51. type: Boolean,
  52. default: false
  53. },
  54. clearable: {
  55. type: Boolean,
  56. default: true
  57. },
  58. // 树形的数据
  59. data: {
  60. type: Array,
  61. default: function () {
  62. return []
  63. }
  64. },
  65. // 每个树节点用来作为唯一标识的属性
  66. nodeKey: {
  67. type: [String, Number],
  68. default: 'id'
  69. },
  70. // 组件大小
  71. size:{
  72. type: String,
  73. default: 'small'
  74. },
  75. // 是否可搜索
  76. filterable: {
  77. type: Boolean,
  78. default: false
  79. },
  80. // 是否禁用
  81. disabled: {
  82. type: Boolean,
  83. default: false
  84. },
  85. // tree的props配置
  86. props: {
  87. type: Object,
  88. default: function () {
  89. return {
  90. label: 'label',
  91. children: 'children'
  92. }
  93. }
  94. },
  95. placeholder:{
  96. type: String,
  97. default: '请选择'
  98. },
  99. },
  100. data() {
  101. return {
  102. optionData: {
  103. id: '',
  104. name: ''
  105. },
  106. filterFlag: false
  107. }
  108. },
  109. watch: {
  110. value: {
  111. // 选择的数据发生变化触发
  112. handler(val) {
  113. if (!this.isEmpty(this.data)) {
  114. this.init(val)
  115. }
  116. },
  117. immediate: true
  118. },
  119. data: function (val) {
  120. if (!this.isEmpty(val)) {
  121. this.init(this.value)
  122. }
  123. }
  124. },
  125. created() {},
  126. methods: {
  127. // 是否为空
  128. isEmpty(val) {
  129. for (let key in val) {
  130. return false
  131. }
  132. return true
  133. },
  134. // 节点被点击时的回调
  135. handleNodeClick(data) {
  136. if (this.multiple) {
  137. return
  138. }
  139. this.$emit('input', data[this.nodeKey])
  140. this.$refs.select.visible = false
  141. // 当点击选中树的节点时,下拉框不会自动隐藏,感觉体验不太好。查看文档,
  142. // select组件也没有控制下拉框显示隐藏的属性,然后在select组件源码中找到了visible属性,用来控制下拉框显示隐藏。
  143. // 点击选中树的节点时设置 this.$refs.select.visible = false 即可。
  144. },
  145. // 节点选中状态发生变化时的回调
  146. handleCheckChange() {
  147. const nodes = this.$refs.tree.getCheckedNodes()
  148. const value = nodes.map((item) => item[this.nodeKey]).join(',')
  149. this.$emit('input', value)
  150. },
  151. init(val) {
  152. // 多选
  153. if (this.multiple) {
  154. const arr = val.toString().split(',')
  155. this.$nextTick(() => {
  156. // setCurrentKey() 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
  157. this.$refs.tree.setCheckedKeys(arr)
  158. // getCheckedNodes() 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
  159. const nodes = this.$refs.tree.getCheckedNodes()
  160. this.optionData.id = val
  161. this.optionData.name = nodes
  162. .map((item) => item[this.props.label])
  163. .join(',')
  164. })
  165. }
  166. // 单选
  167. else {
  168. val = val === '' ? null : val
  169. this.$nextTick(() => {
  170. // setCurrentKey() 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
  171. this.$refs.tree.setCurrentKey(val)
  172. if (val === null) {
  173. return
  174. }
  175. // getNode() 根据 data 或者 key 拿到 Tree 组件中的 node
  176. const node = this.$refs.tree.getNode(val)
  177. this.optionData.id = val
  178. this.optionData.name = node.label
  179. })
  180. }
  181. },
  182. // 下拉框出现/隐藏时触发
  183. visibleChange(e) {
  184. if (e) {
  185. const tree = this.$refs.tree
  186. this.filterFlag && tree.filter('')
  187. this.filterFlag = false
  188. let selectDom = null
  189. if(this.multiple) {
  190. // $el.querySelector() == document.querySelector() // 获取 dom 元素
  191. selectDom = tree.$el.querySelector('.el-tree-node.is-checked')
  192. } else {
  193. selectDom = tree.$el.querySelector('.is-current')
  194. }
  195. setTimeout(() => {
  196. this.$refs.select.scrollToOption({ $el: selectDom })
  197. }, 0)
  198. }
  199. },
  200. // 下拉框点击
  201. clear() {
  202. this.$emit('input', '')
  203. },
  204. // 聚焦
  205. focus(){
  206. this.$emit('focus')
  207. },
  208. // 自定义搜索方法
  209. filterMethod(val) {
  210. this.filterFlag = true
  211. this.$refs.tree.filter(val)
  212. },
  213. // 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
  214. filterNode(value, data) {
  215. if (!value) return true
  216. const label = this.props.label || 'name'
  217. return data[label].indexOf(value) !== -1
  218. }
  219. }
  220. }
  221. </script>
  222. <style lang="scss" scoped>
  223. // 得用 ::v-deep
  224. ::v-deep.tree-select__option {
  225. &.el-select-dropdown__item {
  226. height: auto;
  227. line-height: 1;
  228. padding: 0;
  229. background-color: #fff;
  230. }
  231. }
  232. // 得用 ::v-deep
  233. .tree-select__tree {
  234. padding: 4px 20px;
  235. font-weight: 400;
  236. &.tree-select__tree--radio {
  237. .el-tree-node.is-current > .el-tree-node__content {
  238. //选中字体的样式
  239. color:red;
  240. font-weight: 700;
  241. }
  242. }
  243. }
  244. </style>