km-tree-async.vue 8.3 KB

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