当前位置:   article > 正文

vue3 + element-plus 自定义树形穿梭框_vue3 tree-transfer v-model

vue3 tree-transfer v-model

效果图:

子组件:

HTML

  1. <template>
  2. <div class="treeTransfer">
  3. <!-- 左边 -->
  4. <div class="leftTree">
  5. <div class="list">
  6. <div class="left_lowline">
  7. <el-checkbox v-model="checkedLeft" :disabled="leftTreeData.length < 1" label="" size="large"
  8. style="margin-right: 12px" @change="leftAllCheck" />
  9. <p class="left_title">{{ props.title[0] }}</p>
  10. </div>
  11. <!-- 搜索 -->
  12. <div class="left_input">
  13. <el-input v-model.trem="leftSearchText" class="w-50 m-2" placeholder="请输入部门人员名称" clearable
  14. prefix-icon="el-icon-search" @clear="onSearchLeft" @keyup.enter="onSearchLeft"
  15. @change="onSearchLeft" />
  16. </div>
  17. <el-tree ref="leftTreeRef" :filter-node-method="filterNode" :data="leftTreeData" show-checkbox
  18. :node-key="props.nodeKey" :props="props.defaultProps" v-slot="{ node, data }" accordion
  19. @check="onCheckLeft" default-expand-all>
  20. <div>
  21. {{ data.corgName }}
  22. </div>
  23. <div>
  24. {{ data.label }}
  25. </div>
  26. </el-tree>
  27. </div>
  28. </div>
  29. <!-- 中间按钮 -->
  30. <div class="btnDiv">
  31. <div class="mg10">
  32. <el-button @click="toRight()" size="small" icon="el-icon-right" circle :disabled="leftOperation.length < 1"/>
  33. </div>
  34. <div class="mg10">
  35. <el-button @click="toLeft()" size="small" icon="el-icon-back" circle :disabled="rightOperation.length < 1"/>
  36. </div>
  37. </div>
  38. <!-- 右边 -->
  39. <div class="rightTree">
  40. <div class="list">
  41. <div class="left_lowline">
  42. <el-checkbox v-model="checkedRight" :disabled="rightTreeData.length < 1" label="" size="large"
  43. style="margin-right: 12px" @change="rightAllCheck" />
  44. <p class="left_title">{{ props.title[1] }}</p>
  45. </div>
  46. <!-- 搜索 -->
  47. <div class="left_input">
  48. <el-input v-model.trem="rightSearchText" class="w-50 m-2" placeholder="请输入部门人员名称" clearable
  49. prefix-icon="el-icon-search" @clear="onSearchRight" @keyup.enter="onSearchRight"
  50. @change="onSearchRight" />
  51. </div>
  52. <el-tree ref="rightTreeRef" :data="rightTreeData" show-checkbox :node-key="props.nodeKey"
  53. :props="props.defaultProps" v-slot="{ node, data }" accordion @check="onCheckRight" default-expand-all>
  54. <div>
  55. {{ data.corgName }}
  56. </div>
  57. <div>
  58. {{ data.label }}
  59. </div>
  60. </el-tree>
  61. </div>
  62. </div>
  63. </div>
  64. </template>

 script:

  1. <script setup lang="ts">
  2. //@ts-nocheck 解决ts类型报红
  3. import { ref, onMounted, watch } from 'vue';
  4. import lodash from 'lodash'
  5. const props = defineProps(['nodeKey', 'fromData', 'toData', 'defaultProps', 'title']);
  6. const checkedLeft = ref(false)
  7. const checkedRight = ref(false)
  8. const leftSearchText = ref('');
  9. const rightSearchText = ref('');
  10. const leftOperation = ref<any[]>([])
  11. const rightOperation = ref<any[]>([])
  12. // 定义emit
  13. const emits = defineEmits(['change']);
  14. const leftTreeRef = ref();
  15. const rightTreeRef = ref();
  16. // 左侧数据
  17. const leftTreeData = ref([]);
  18. // 右侧数据
  19. const rightTreeData = ref([]);
  20. watch(
  21. props,
  22. newVal => {
  23. leftTreeData.value = lodash.cloneDeep(newVal.fromData)
  24. rightTreeData.value = lodash.cloneDeep(newVal.toData)
  25. },
  26. { immediate: true }
  27. )
  28. watch(leftSearchText, val => {
  29. rightTreeRef.value?.filter(val)
  30. })
  31. watch(rightSearchText, val => {
  32. leftTreeRef.value?.filter(val)
  33. })
  34. defineExpose({
  35. leftTreeData,
  36. rightTreeData
  37. })
  38. onMounted(() => {
  39. leftSearchText.value = ''
  40. rightSearchText.value = ''
  41. })
  42. const removeChecked = (rightList, allCheckedKeys) => {
  43. rightList.forEach((e, index) => {
  44. if (allCheckedKeys.indexOf(e.id) == -1) {
  45. rightList.splice(index, 1)
  46. }
  47. if (e.children) {
  48. removeChecked(e.children, allCheckedKeys)
  49. }
  50. })
  51. return rightList
  52. }
  53. const recursionTree = (parents, allCheckedKeys) => {
  54. parents.forEach(e => {
  55. const list = lodash.filter(e.children, child => allCheckedKeys.indexOf(child.id) >= 0)
  56. e.children = list
  57. if (e.children.length !== 0) {
  58. recursionTree(e.children, allCheckedKeys)
  59. }
  60. })
  61. return parents
  62. }
  63. const handleData = (rightNodes, leftTree, allCheckedKeys) => {
  64. rightNodes.forEach(item => {
  65. if (item.children) {
  66. let parent = leftTree.getNode(item.pid)
  67. let node = item
  68. if (!leftTree.getNode(item.id) && allCheckedKeys.indexOf(item.id) > 0) {
  69. const copyNode = lodash.cloneDeep(node)
  70. copyNode.children = []
  71. leftTree.append(copyNode, parent)
  72. }
  73. handleData(item.children, leftTree, allCheckedKeys)
  74. } else {
  75. let parent = leftTree.getNode(item.pid)
  76. let node = item
  77. if (!leftTree.getNode(item.id) && allCheckedKeys.indexOf(item.id) > 0) {
  78. leftTree.append(node, parent)
  79. }
  80. }
  81. })
  82. }
  83. // // 去右边
  84. const toRight = async () => {
  85. const leftTree = leftTreeRef.value;
  86. checkedLeft.value = false
  87. if (!leftTree) {
  88. return
  89. }
  90. const leftNodes = leftTree.getCheckedNodes(false, true)
  91. const parents = leftNodes.filter(
  92. el => el.children && el.children.length > 0 && el.pid == '0'
  93. )
  94. const checkedKeys = leftTree.getCheckedKeys(false)
  95. const halfCheckedKeys = leftTree.getHalfCheckedKeys()
  96. const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)
  97. const rightTree = rightTreeRef.value
  98. if (!rightTree) {
  99. let rightList = parents.map(parent => {
  100. const obj = lodash.omit(parent, ['children'])
  101. obj.children = lodash.filter(parent.children, child => allCheckedKeys.indexOf(child.id) >= 0)
  102. return obj
  103. })
  104. let copyRightList = lodash.cloneDeep(rightList)
  105. removeChecked(copyRightList, allCheckedKeys)
  106. rightTreeData.value.push(...copyRightList)
  107. } else {
  108. let isExist = false
  109. rightTree.data.forEach(e => {
  110. if (parents[0].id === (e as any).id) isExist = true
  111. })
  112. if (!isExist) {
  113. const overall = lodash.cloneDeep(parents)
  114. let rightList = await recursionTree(overall, allCheckedKeys)
  115. let copyRightList = lodash.cloneDeep(rightList)
  116. removeChecked(copyRightList, allCheckedKeys)
  117. rightTreeData.value.push(...copyRightList)
  118. } else {
  119. handleData(leftNodes, rightTree, allCheckedKeys)
  120. }
  121. }
  122. // 移过去之后要删除原本的值
  123. leftNodes.forEach(node => {
  124. leftTree.setChecked(node, false, false)
  125. if (checkedKeys.indexOf(node.id) >= 0) {
  126. leftTree.remove(node)
  127. }
  128. })
  129. leftOperation.value = formatTree(leftTree.getCheckedNodes(false) || [], 'pid', 'id');
  130. // emits('change', leftTreeData.value, rightTreeData.value)
  131. emits('change', checkedKeys)
  132. setTimeout(() => {
  133. rightTree?.setCheckedNodes(leftNodes);
  134. rightOperation.value = formatTree(rightTree.getCheckedNodes(false) || [], 'pid', 'id')
  135. }, 500)
  136. };
  137. // // 去左边
  138. const toLeft = async () => {
  139. checkedRight.value = false
  140. const rightTree = rightTreeRef.value
  141. if (!rightTree) {
  142. return
  143. }
  144. const rightNodes = rightTree.getCheckedNodes(false, true)
  145. const checkedKeys = rightTree.getCheckedKeys(false)
  146. const halfCheckedKeys = rightTree.getHalfCheckedKeys()
  147. const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)
  148. const parents = lodash.filter(
  149. rightNodes,
  150. item => item.children && item.children.length > 0 && item.pid == '0'
  151. )
  152. const leftTree = leftTreeData.value.length ? leftTreeRef.value : null
  153. if (!leftTree) {
  154. let leftList = parents.map(parent => {
  155. const obj = lodash.omit(parent, ['children'])
  156. obj.children = lodash.filter(parent.children, child => allCheckedKeys.indexOf(child.id) >= 0)
  157. return obj
  158. })
  159. let copyLeftList = lodash.cloneDeep(leftList)
  160. removeChecked(copyLeftList, allCheckedKeys)
  161. leftTreeData.value.push(...copyLeftList)
  162. } else {
  163. let isExist = false
  164. leftTree.data.forEach(e => {
  165. if (parents[0].id === (e as any).id) isExist = true
  166. })
  167. if (!isExist) {
  168. const overall = lodash.cloneDeep(parents)
  169. let leftList = await recursionTree(overall, allCheckedKeys)
  170. let copyLeftList = lodash.cloneDeep(leftList)
  171. removeChecked(copyLeftList, allCheckedKeys)
  172. rightTreeData.value.push(...copyLeftList)
  173. } else {
  174. handleData(rightNodes, leftTree, allCheckedKeys)
  175. }
  176. }
  177. console.log(leftTreeData.value, 'slf-leftTreeData');
  178. rightNodes.forEach(node => {
  179. rightTree.setChecked(node, false, false)
  180. if (checkedKeys.indexOf(node.id) >= 0) {
  181. rightTree.remove(node)
  182. }
  183. })
  184. rightOperation.value = formatTree(rightTree.getCheckedNodes(false) || [], 'pid', 'id')
  185. // emits('change', leftTreeData.value, rightTreeData.value)
  186. emits('change', checkedKeys)
  187. };
  188. //左侧选中(移除原本值用)
  189. const onCheckLeft = () => {
  190. leftOperation.value = formatTree(
  191. leftTreeRef.value?.getCheckedNodes(false) || [],
  192. 'pid',
  193. 'id'
  194. )
  195. }
  196. // 右侧选中
  197. const onCheckRight = () => {
  198. rightOperation.value = formatTree(
  199. rightTreeRef.value!.getCheckedNodes(false) || [],
  200. 'pid',
  201. 'id'
  202. )
  203. }
  204. const formatTree = (tree: any[], parentKey: string = 'pid', idKey: string = 'id') => {
  205. // 格式化选择的树:清除全选下面的子节点
  206. let swap,
  207. parentIds: string[] = []
  208. // 先找出有children的id集合,再把所有的数据做对比,只要pid和其中一个对上,就把该数据删除;
  209. tree.forEach((item, index) => {
  210. if (item.children) {
  211. parentIds.push(item[idKey])
  212. }
  213. })
  214. swap = tree.filter((item, index) => {
  215. if (parentIds.indexOf(item[parentKey]) == -1) {
  216. return item
  217. }
  218. })
  219. return swap
  220. }
  221. const leftAllCheck = () => {
  222. const leftTree = leftTreeRef.value;
  223. if (checkedLeft.value) {
  224. leftTree?.setCheckedNodes(leftTreeData.value)
  225. checkedLeft.value = true;
  226. } else {
  227. leftTree?.setCheckedNodes([])
  228. checkedLeft.value = false
  229. }
  230. }
  231. const onSearchLeft = () => {
  232. leftTreeRef.value?.filter(leftSearchText.value)
  233. }
  234. const rightAllCheck = () => {
  235. const rightTree = rightTreeRef.value
  236. if (checkedRight.value) {
  237. rightTree?.setCheckedNodes(rightTreeData.value)
  238. checkedRight.value = true
  239. } else {
  240. rightTree?.setCheckedNodes([])
  241. checkedRight.value = false
  242. }
  243. }
  244. const onSearchRight = () => {
  245. rightTreeRef.value!.filter(rightSearchText.value)
  246. }
  247. const filterNode = (value: string, data: any) => {
  248. if (!value) return true
  249. return data.label.includes(value)
  250. }

父组件引用:

  1. <TreeTransfer ref="treeTransferRef" node-key="id" :fromData="fromData" :toData="toData"
  2. :defaultProps="transferProps" @change="treeChange" :title="['选择人员', '已选择人员']">
  3. </TreeTransfer>
  1. let treeTransferRef = ref(); // 树形穿梭框
  2. let fromData = ref([
  3. {
  4. id: "1",
  5. pid: 0, //自定义pid的参数名,默认为"pid" 必填:false
  6. label: "一级 1",
  7. children: [
  8. {
  9. id: "1-1",
  10. pid: "1",
  11. label: "二级 1-1",
  12. children: []
  13. },
  14. {
  15. id: "1-2",
  16. pid: "1",
  17. label: "二级 1-2",
  18. children: [
  19. {
  20. id: "1-2-1",
  21. pid: "1-2",
  22. children: [],
  23. label: "二级 1-2-1"
  24. },
  25. {
  26. id: "1-2-2",
  27. pid: "1-2",
  28. children: [],
  29. label: "二级 1-2-2"
  30. },
  31. {
  32. id: "1-2-3",
  33. pid: "1-2",
  34. children: [],
  35. label: "二级 1-2-3"
  36. }
  37. ]
  38. }
  39. ]
  40. }
  41. ]); // 树形数据
  42. let toData = ref([]); // 选中的ids数据
  43. const transferProps = ref({
  44. label: 'label',
  45. children: 'children',
  46. disabled: 'disabled',
  47. });

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/223916
推荐阅读
相关标签
  

闽ICP备14008679号