km-tree.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <a-card :class="[{ [$style.treewrap]: !hideLeftBtn }, { [$style.collapse]: fold }]">
  3. <a-input-search
  4. v-if="treeData.length > 0 && !hideSearch"
  5. placeholder="请输入"
  6. allow-clear
  7. @change="onSearchChange"
  8. />
  9. <a-spin :spinning="spinning" :class="$style.spin" />
  10. <a-empty v-if="empty" />
  11. <a-tree
  12. v-if="treeData.length > 0"
  13. show-icon
  14. :expanded-keys.sync="expandedKeys"
  15. :auto-expand-parent="autoExpandParent"
  16. :tree-data="treeData"
  17. :replace-fields="replacefields"
  18. :selected-keys.sync="selectedkey"
  19. @select="onSelect"
  20. @expand="onExpand"
  21. >
  22. <template v-slot:title="{ text }">
  23. <span v-if="text.indexOf(searchValue) > -1">
  24. {{ text.substr(0, text.indexOf(searchValue))
  25. }}<span :class="$style.searchColor">{{ searchValue }}</span
  26. >{{ text.substr(text.indexOf(searchValue) + searchValue.length) }}</span
  27. ><span v-else>{{ text }}</span>
  28. </template>
  29. </a-tree>
  30. <a-button v-if="!hideLeftBtn" type="primary" :class="$style.fold" @click="foldClick">
  31. <a-icon :type="icontype" />
  32. </a-button>
  33. </a-card>
  34. </template>
  35. <script>
  36. import lodash from 'lodash'
  37. import axios from '@/common/services/axios-instance'
  38. import components from './_import-components/km-tree-import'
  39. export default {
  40. name: 'KmTree',
  41. components,
  42. props: {
  43. dataUrl: {
  44. type: String,
  45. required: true,
  46. },
  47. hideLeftBtn: {
  48. type: Boolean,
  49. default: false,
  50. },
  51. hideSearch: {
  52. type: Boolean,
  53. default: false,
  54. },
  55. // 默认选中的节点
  56. defaultselectedkey: {
  57. type: String,
  58. default: '',
  59. },
  60. },
  61. data() {
  62. return {
  63. searchValue: '',
  64. dataList: [], // 数组dataList,搜索要用
  65. spinning: true,
  66. empty: false,
  67. treeData: [],
  68. expandedKeys: [],
  69. autoExpandParent: true,
  70. replacefields: {
  71. title: 'text',
  72. key: 'id',
  73. },
  74. selectedkey: [], // 选中的节点
  75. icontype: 'left',
  76. fold: false,
  77. }
  78. },
  79. watch: {
  80. // prop变化时,刷新表格
  81. dataUrl() {
  82. this.loadData()
  83. },
  84. defaultselectedkey(value) {
  85. this.selectedkey = [value]
  86. this.expandedKeys = [value]
  87. this.fnGetNodeItem(this.treeData, value).then((item) => {
  88. this.$emit('treeSelectd', item)
  89. })
  90. },
  91. },
  92. created() {
  93. this.loadData()
  94. },
  95. methods: {
  96. transformData(data) {
  97. return data.map((d) => {
  98. const { children, checkable, selectable, leaf, ...rest } = d
  99. return {
  100. ...rest,
  101. checkable: checkable ?? false,
  102. selectable: selectable ?? true,
  103. isLeaf: leaf ?? true,
  104. key: d[this.optionValue],
  105. title: d[this.optionName],
  106. children: children && this.transformData(children),
  107. scopedSlots: { title: 'title' },
  108. originalValue: rest,
  109. }
  110. })
  111. },
  112. // 搜索相关
  113. onExpand(expandedKeys) {
  114. this.expandedKeys = expandedKeys
  115. this.autoExpandParent = false
  116. },
  117. getParentKey(key, tree) {
  118. let parentKey
  119. for (let i = 0; i < tree.length; i++) {
  120. const node = tree[i]
  121. if (node.children) {
  122. if (node.children.some((item) => item.id === key)) {
  123. parentKey = node.id
  124. } else if (this.getParentKey(key, node.children)) {
  125. parentKey = this.getParentKey(key, node.children)
  126. }
  127. }
  128. }
  129. return parentKey
  130. },
  131. // 获取当前选中节点的根节点
  132. getRootKey(key) {
  133. let rootKey
  134. const strKey = key.toString()
  135. if (this.treeData.length === 1) {
  136. rootKey = this.treeData[0].id
  137. } else {
  138. for (let index = 0; index < this.treeData.length; index++) {
  139. var item = this.treeData[index]
  140. // 先判断选中的是不是根节点
  141. if (item.id === strKey) {
  142. rootKey = key
  143. break
  144. } else {
  145. if (this.getParentKey(strKey, [item])) {
  146. // 说明选中的是这个根节点下的分类
  147. rootKey = item.id
  148. break
  149. }
  150. }
  151. }
  152. }
  153. return parseInt(rootKey)
  154. },
  155. onSearchChange(e) {
  156. const value = e.target.value
  157. const expandedKeys = this.dataList
  158. .map((item) => {
  159. if (item.title.indexOf(value) > -1) {
  160. return this.getParentKey(item.key, this.treeData)
  161. }
  162. return null
  163. })
  164. .filter((item, i, self) => item && self.indexOf(item) === i)
  165. Object.assign(this, {
  166. expandedKeys,
  167. searchValue: value,
  168. autoExpandParent: true,
  169. })
  170. },
  171. // 处理搜索用的dataList
  172. generateList(data) {
  173. for (let i = 0; i < data.length; i++) {
  174. const node = data[i]
  175. const key = node.id
  176. const title = node.text
  177. const props = node.props
  178. this.dataList.push({ key, id: key, title: title, props })
  179. if (node.children) {
  180. this.generateList(node.children)
  181. }
  182. }
  183. },
  184. // 获取树数据
  185. loadData() {
  186. const req = {
  187. url: this.dataUrl,
  188. method: 'get',
  189. }
  190. return axios(req).then((res) => {
  191. this.spinning = false
  192. if (res.data) {
  193. let newArr = []
  194. if (lodash.isArray(res.data)) {
  195. newArr = res.data
  196. } else {
  197. if (lodash.isObject(res.data)) {
  198. newArr.push(res.data)
  199. }
  200. }
  201. this.treeData = this.transformData(newArr)
  202. this.empty = false
  203. if (this.defaultselectedkey) {
  204. this.expandedKeys = [this.defaultselectedkey] // 默认展开的结点
  205. this.selectedkey = [this.defaultselectedkey]
  206. this.fnGetNodeItem(this.treeData, this.defaultselectedkey).then((item) => {
  207. this.$emit('treeSelectd', item)
  208. })
  209. } else {
  210. this.expandedKeys = [newArr[0].id]
  211. this.selectedkey = [newArr[0].id]
  212. this.$emit('treeSelectd', res.data[0])
  213. }
  214. // 生成搜索要用的数组
  215. this.generateList(this.treeData)
  216. } else {
  217. this.treeData = []
  218. this.empty = true
  219. this.$emit('treeSelectd')
  220. }
  221. })
  222. },
  223. // 获取被选中的节点
  224. fnGetNodeItem(data, key) {
  225. return new Promise((resolve, reject) => {
  226. for (var i in data) {
  227. if (data[i].id === key) {
  228. resolve(data[i])
  229. break
  230. } else {
  231. if (data[i].children) {
  232. this.fnGetNodeItem(data[i].children, key).then((data) => {
  233. resolve(data)
  234. })
  235. }
  236. }
  237. }
  238. })
  239. },
  240. // 刷新树
  241. refresh() {
  242. this.treeData = []
  243. this.spinning = true
  244. const req = {
  245. url: this.dataUrl,
  246. method: 'get',
  247. }
  248. return axios(req).then((res) => {
  249. this.spinning = false
  250. if (res.data) {
  251. let newArr = []
  252. if (lodash.isArray(res.data)) {
  253. newArr = res.data
  254. } else {
  255. if (lodash.isObject(res.data)) {
  256. newArr.push(res.data)
  257. }
  258. }
  259. this.treeData = this.transformData(newArr)
  260. this.empty = false
  261. // 生成搜索要用的数组
  262. this.generateList(this.treeData)
  263. // this.expandedKeys = [newArr[0].id] // 默认展开顶结点
  264. } else {
  265. this.treeData = []
  266. this.empty = true
  267. }
  268. })
  269. },
  270. // 树选中调用
  271. onSelect(selectedKeys, info) {
  272. this.selectedkey = selectedKeys
  273. this.$emit('treeSelectd', info)
  274. },
  275. // 展示/隐藏树的小箭头点击事件
  276. foldClick() {
  277. this.fold = !this.fold
  278. if (this.fold) {
  279. this.icontype = 'right'
  280. } else {
  281. this.icontype = 'left'
  282. }
  283. },
  284. },
  285. }
  286. </script>
  287. <style module lang="scss">
  288. @use '@/common/design' as *;
  289. .spin {
  290. width: 100%;
  291. line-height: 30;
  292. }
  293. .treewrap {
  294. :global(.ant-input-search) {
  295. margin: 8px 0;
  296. overflow: hidden;
  297. }
  298. position: relative;
  299. display: flex;
  300. flex-direction: column;
  301. width: 20%;
  302. min-height: 100%;
  303. margin-right: $padding-lg;
  304. transition: width 0.2s;
  305. .fold {
  306. position: absolute;
  307. top: calc(50% - 30px);
  308. right: -15px;
  309. z-index: 2;
  310. width: 15px;
  311. height: 75px;
  312. padding: 0;
  313. border-radius: 0 10px 10px 0;
  314. }
  315. :global(.ant-tree) {
  316. overflow-x: auto;
  317. text-overflow: ellipsis;
  318. white-space: nowrap;
  319. }
  320. :global(.ant-card-body) {
  321. background: $white;
  322. }
  323. }
  324. .collapse {
  325. width: 0;
  326. :global(.ant-card-body) {
  327. background: transparent;
  328. :global(.ant-empty) {
  329. display: none;
  330. }
  331. }
  332. }
  333. .search-color {
  334. color: $primary-color;
  335. }
  336. </style>