SelectTree.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <template>
  2. <div class="select-tree">
  3. <div class="left">
  4. <el-input
  5. v-model="queryStr"
  6. :style="{ width: '300px', height: '30px' }"
  7. placeholder="请输入搜索内容"
  8. :prefix-icon="Search"
  9. @keyup.enter="onSearch"
  10. />
  11. <el-tree
  12. ref="treeRef"
  13. :data="filterData"
  14. show-checkbox
  15. node-key="id"
  16. :props="defaultProps"
  17. :default-expand-all="true"
  18. @node-click="handleNodeClick"
  19. @check-change="handleCheckChange"
  20. />
  21. </div>
  22. <el-divider direction="vertical" style="height: auto" />
  23. <div class="right" style="margin-left: 16px">
  24. <div class="head" style="margin-bottom: 22px">
  25. <span
  26. style="font-weight: 400; font-size: 16px; color: rgba(0, 0, 0, 0.88); line-height: 22px"
  27. >已选择({{ selected }}</span
  28. >
  29. <span
  30. style="
  31. margin-left: 4px;
  32. font-size: 10px;
  33. font-weight: 400;
  34. color: rgba(0, 0, 0, 0.45);
  35. line-height: 22px;
  36. "
  37. >/&nbsp;{{ total }})</span
  38. >
  39. </div>
  40. <div class="selected">
  41. <el-tag
  42. v-for="(person, index) in selectedPeople"
  43. :key="index"
  44. closable
  45. @close="handleTagClose(person.id)"
  46. >
  47. {{ person.name }}
  48. </el-tag>
  49. </div>
  50. <div class="footer">
  51. <el-button @click="handleCancle"> 取消 </el-button>
  52. <el-button type="primary" @click="handleSubmit">提交</el-button>
  53. </div>
  54. </div>
  55. </div>
  56. </template>
  57. <script lang="ts" setup>
  58. import { onMounted, ref, watch } from 'vue';
  59. import { Search } from '@element-plus/icons-vue';
  60. import type { ElTree } from 'element-plus';
  61. import { treeSelected, TreeNode, FormattedNode } from '../type';
  62. import { countLeafNodes, formatTree } from '../hook/index';
  63. import { queryUserTree } from '../api/index';
  64. import { cloneDeep } from 'lodash-es';
  65. const queryStr = ref<string>('');
  66. const defaultProps = {
  67. children: 'children',
  68. label: 'name',
  69. };
  70. const treeData = ref<TreeNode[]>();
  71. const nodeData = ref<FormattedNode[]>();
  72. const filterData = ref<FormattedNode[]>();
  73. const filterTree = (nodes: FormattedNode[], keyword: string): FormattedNode[] => {
  74. const filteredNodes: FormattedNode[] = [];
  75. const traverse = (node) => {
  76. let includeNode = false;
  77. if (node.name.includes(keyword)) {
  78. includeNode = true;
  79. }
  80. if (node.children) {
  81. const filteredChildren = node.children
  82. .map((child) => traverse(child))
  83. .filter((child) => child !== null);
  84. if (filteredChildren.length > 0) {
  85. includeNode = true;
  86. node = { ...node, children: filteredChildren };
  87. }
  88. }
  89. return includeNode ? node : null;
  90. };
  91. nodes.forEach((node) => {
  92. const result = traverse(node);
  93. if (result !== null) {
  94. filteredNodes.push(result);
  95. }
  96. });
  97. return filteredNodes;
  98. };
  99. const onSearch = () => {
  100. if (queryStr.value) {
  101. filterData.value = filterTree(nodeData.value!, queryStr.value);
  102. } else {
  103. filterData.value = nodeData.value;
  104. }
  105. queryStr.value = '';
  106. };
  107. const total = ref<number>(0);
  108. const selected = ref<number>(0);
  109. const selectedPeople = ref<treeSelected[]>([]);
  110. const handleCheckChange = (node, checked) => {
  111. if (!node.children || (node.children.length === 0 && node.userId)) {
  112. if (checked) {
  113. selectedPeople.value.push({
  114. id: node.id,
  115. name: node.name,
  116. userId: node.userId,
  117. });
  118. } else {
  119. const index = selectedPeople.value.findIndex((item) => item.id === node.id);
  120. if (index !== -1) {
  121. selectedPeople.value.splice(index, 1);
  122. }
  123. }
  124. selected.value = selectedPeople.value.length;
  125. }
  126. };
  127. const treeRef = ref<InstanceType<typeof ElTree>>();
  128. const handleTagClose = (id) => {
  129. const index = selectedPeople.value.findIndex((item) => item.id === id);
  130. if (index !== -1) {
  131. selectedPeople.value.splice(index, 1);
  132. selected.value = selectedPeople.value.length;
  133. treeRef.value!.setChecked(id, false, true);
  134. }
  135. };
  136. const emit = defineEmits(['cancel', 'submit']);
  137. const handleCancle = () => {
  138. emit('cancel');
  139. };
  140. const handleSubmit = () => {
  141. emit('submit', selectedPeople.value);
  142. };
  143. const handleNodeClick = (node) => {
  144. const isChecked = treeRef.value!.getCheckedKeys().includes(node.id);
  145. treeRef.value!.setChecked(node.id,!isChecked,true);
  146. };
  147. const props = defineProps<{
  148. selectedUser: treeSelected[];
  149. }>();
  150. onMounted(() => {
  151. queryUserTree().then((res) => {
  152. treeData.value = res;
  153. nodeData.value = formatTree(treeData.value!);
  154. filterData.value = nodeData.value;
  155. total.value = countLeafNodes(nodeData.value);
  156. const selectedIds: string[] = selectedPeople.value.map((item) => item.id as string);
  157. treeRef.value!.setCheckedKeys(selectedIds);
  158. });
  159. });
  160. watch(
  161. () => props.selectedUser,
  162. (newSelected) => {
  163. selectedPeople.value = cloneDeep(newSelected);
  164. selected.value = selectedPeople.value.length;
  165. },
  166. { immediate: true },
  167. );
  168. </script>
  169. <style lang="scss" scoped>
  170. .select-tree {
  171. display: flex;
  172. width: 100%;
  173. height: 100%;
  174. .left {
  175. display: flex;
  176. flex-direction: column;
  177. width: 50%;
  178. height: 100%;
  179. .el-tree {
  180. width: 100%;
  181. margin-top: 20px;
  182. font-weight: 500;
  183. font-size: 14px;
  184. color: #303133;
  185. line-height: 22px;
  186. overflow-y: auto;
  187. max-height: calc(100% - 80px);
  188. }
  189. }
  190. .right {
  191. display: flex;
  192. flex-direction: column;
  193. width: 50%;
  194. height: 100%;
  195. position: relative;
  196. .head {
  197. display: flex;
  198. align-items: center;
  199. font-weight: 400;
  200. font-size: 16px;
  201. color: rgba(0, 0, 0, 0.88);
  202. line-height: 22px;
  203. }
  204. .selected {
  205. display: flex;
  206. flex-wrap: wrap;
  207. gap: 8px;
  208. overflow-y: auto;
  209. max-height: calc(100% - 120px);
  210. }
  211. .footer {
  212. display: flex;
  213. gap: 6px;
  214. position: absolute;
  215. right: 24px;
  216. bottom: 20px;
  217. }
  218. }
  219. }
  220. </style>