index.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <!-- 树状选择器 -->
  2. <template>
  3. <el-popover
  4. ref="popover"
  5. placement="bottom-start"
  6. trigger="click"
  7. @show="onShowPopover"
  8. @hide="onHidePopover"
  9. >
  10. <el-tree
  11. ref="tree"
  12. :expand-on-click-node="false"
  13. :style="`min-width: ${treeWidth}`"
  14. :data="data"
  15. :props="props"
  16. :filter-node-method="filterNode"
  17. placeholder="选择部门"
  18. class="select-tree"
  19. check-strictly="false"
  20. highlight-current
  21. default-expand-all
  22. @node-click="onClickNode"
  23. />
  24. <el-input
  25. slot="reference"
  26. ref="input"
  27. v-model="labelModel"
  28. :style="`width: ${width}px`"
  29. :class="{ rotate: showStatus }"
  30. :placeholder="placeholder"
  31. clearable
  32. suffix-icon="el-icon-arrow-down"
  33. />
  34. </el-popover>
  35. </template>
  36. <script>
  37. export default {
  38. name: 'DepartTree',
  39. // 设置绑定参数
  40. model: {
  41. prop: 'value',
  42. event: 'selected'
  43. },
  44. props: {
  45. // 接收绑定参数
  46. // eslint-disable-next-line vue/require-default-prop
  47. value: String,
  48. // 输入框宽度
  49. // eslint-disable-next-line vue/require-default-prop
  50. width: String,
  51. // 选项数据
  52. options: {
  53. type: Array,
  54. required: true
  55. },
  56. // 输入框占位符
  57. placeholder: {
  58. type: String,
  59. required: false,
  60. default: '请选择'
  61. },
  62. // 树节点配置选项
  63. props: {
  64. type: Object,
  65. required: false,
  66. default: () => ({
  67. parent: 'parentId',
  68. value: 'rowGuid',
  69. label: 'areaName',
  70. children: 'children'
  71. })
  72. }
  73. },
  74. data() {
  75. return {
  76. // 树状菜单显示状态
  77. showStatus: false,
  78. // 菜单宽度
  79. treeWidth: 'auto',
  80. // 输入框显示值
  81. labelModel: '',
  82. // 实际请求传值
  83. valueModel: '0'
  84. }
  85. },
  86. computed: {
  87. // 是否为树状结构数据
  88. dataType() {
  89. const jsonStr = JSON.stringify(this.options)
  90. return jsonStr.indexOf(this.props.children) !== -1
  91. },
  92. // 若非树状结构,则转化为树状结构数据
  93. data() {
  94. return this.dataType ? this.options : this.switchTree()
  95. }
  96. },
  97. watch: {
  98. labelModel(val) {
  99. if (!val) {
  100. this.valueModel = ''
  101. }
  102. this.$refs.tree.filter(val)
  103. },
  104. value(val) {
  105. this.labelModel = this.queryTree(this.data, val)
  106. }
  107. },
  108. created() {
  109. // 检测输入框原有值并显示对应 label
  110. if (this.value) {
  111. this.labelModel = this.queryTree(this.data, this.value)
  112. }
  113. // 获取输入框宽度同步至树状菜单宽度
  114. this.$nextTick(() => {
  115. this.treeWidth = `${
  116. (this.width || this.$refs.input.$refs.input.clientWidth) - 24
  117. }px`
  118. })
  119. },
  120. methods: {
  121. // 单击节点
  122. onClickNode(node) {
  123. this.labelModel = node[this.props.label]
  124. this.valueModel = node[this.props.value]
  125. this.onCloseTree()
  126. },
  127. // 偏平数组转化为树状层级结构
  128. switchTree() {
  129. return this.cleanChildren(this.buildTree(this.options, '0'))
  130. },
  131. // 隐藏树状菜单
  132. onCloseTree() {
  133. this.$refs.popover.showPopper = false
  134. },
  135. // 显示时触发
  136. onShowPopover() {
  137. this.showStatus = true
  138. this.$refs.tree.filter(false)
  139. },
  140. // 隐藏时触发
  141. onHidePopover() {
  142. this.showStatus = false
  143. this.$emit('selected', this.valueModel)
  144. },
  145. // 树节点过滤方法
  146. filterNode(query, data) {
  147. if (!query) return true
  148. return data[this.props.label].indexOf(query) !== -1
  149. },
  150. // 搜索树状数据中的 ID
  151. queryTree(tree, id) {
  152. let stark = []
  153. stark = stark.concat(tree)
  154. while (stark.length) {
  155. const temp = stark.shift()
  156. if (temp[this.props.children]) {
  157. stark = stark.concat(temp[this.props.children])
  158. }
  159. if (temp[this.props.value] === id) {
  160. return temp[this.props.label]
  161. }
  162. }
  163. return ''
  164. },
  165. // 将一维的扁平数组转换为多层级对象
  166. buildTree(data, id = '0') {
  167. const fa = (parentId) => {
  168. const temp = []
  169. for (let i = 0; i < data.length; i++) {
  170. const n = data[i]
  171. if (n[this.props.parent] === parentId) {
  172. n.children = fa(n.rowGuid)
  173. temp.push(n)
  174. }
  175. }
  176. return temp
  177. }
  178. return fa(id)
  179. },
  180. // 清除空 children项
  181. cleanChildren(data) {
  182. const fa = (list) => {
  183. list.map((e) => {
  184. if (e.children.length) {
  185. fa(e.children)
  186. } else {
  187. delete e.children
  188. }
  189. return e
  190. })
  191. return list
  192. }
  193. return fa(data)
  194. }
  195. }
  196. }
  197. </script>
  198. <style>
  199. .el-input.el-input--suffix {
  200. cursor: pointer;
  201. overflow: hidden;
  202. }
  203. .el-input.el-input--suffix.rotate .el-input__suffix {
  204. transform: rotate(180deg);
  205. }
  206. .select-tree {
  207. max-height: 350px;
  208. overflow-y: scroll;
  209. }
  210. /* 菜单滚动条 */
  211. .select-tree::-webkit-scrollbar {
  212. z-index: 11;
  213. width: 6px;
  214. }
  215. .select-tree::-webkit-scrollbar-track,
  216. .select-tree::-webkit-scrollbar-corner {
  217. background: #fff;
  218. }
  219. .select-tree::-webkit-scrollbar-thumb {
  220. border-radius: 5px;
  221. width: 6px;
  222. background: #b4bccc;
  223. }
  224. .select-tree::-webkit-scrollbar-track-piece {
  225. background: #fff;
  226. width: 6px;
  227. }
  228. </style>