tree.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <script setup lang="ts">
  2. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  3. import { ref, computed, watch, getCurrentInstance } from "vue";
  4. import Dept from "@iconify-icons/ri/git-branch-line";
  5. // import Reset from "@iconify-icons/ri/restart-line";
  6. import More2Fill from "@iconify-icons/ri/more-2-fill";
  7. import OfficeBuilding from "@iconify-icons/ep/office-building";
  8. import LocationCompany from "@iconify-icons/ep/add-location";
  9. import ExpandIcon from "./svg/expand.svg?component";
  10. import UnExpandIcon from "./svg/unexpand.svg?component";
  11. interface Tree {
  12. id: number;
  13. name: string;
  14. highlight?: boolean;
  15. children?: Tree[];
  16. }
  17. const props = defineProps({
  18. treeLoading: Boolean,
  19. treeData: Array
  20. });
  21. const emit = defineEmits(["tree-select"]);
  22. const treeRef = ref();
  23. const isExpand = ref(true);
  24. const searchValue = ref("");
  25. const highlightMap = ref({});
  26. const { proxy } = getCurrentInstance();
  27. const defaultProps = {
  28. children: "children",
  29. label: "name"
  30. };
  31. const buttonClass = computed(() => {
  32. return [
  33. "!h-[20px]",
  34. "reset-margin",
  35. "!text-gray-500",
  36. "dark:!text-white",
  37. "dark:hover:!text-primary"
  38. ];
  39. });
  40. const filterNode = (value: string, data: Tree) => {
  41. if (!value) return true;
  42. return data.name.includes(value);
  43. };
  44. function nodeClick(value) {
  45. const nodeId = value.$treeNodeId;
  46. highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight
  47. ? Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
  48. highlight: false
  49. })
  50. : Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
  51. highlight: true
  52. });
  53. Object.values(highlightMap.value).forEach((v: Tree) => {
  54. if (v.id !== nodeId) {
  55. v.highlight = false;
  56. }
  57. });
  58. emit(
  59. "tree-select",
  60. highlightMap.value[nodeId]?.highlight
  61. ? Object.assign({ ...value, selected: true })
  62. : Object.assign({ ...value, selected: false })
  63. );
  64. }
  65. function toggleRowExpansionAll(status) {
  66. isExpand.value = status;
  67. const nodes = (proxy.$refs["treeRef"] as any).store._getAllNodes();
  68. for (let i = 0; i < nodes.length; i++) {
  69. nodes[i].expanded = status;
  70. }
  71. }
  72. /** 重置部门树状态(选中状态、搜索框值、树初始化) */
  73. function onTreeReset() {
  74. highlightMap.value = {};
  75. searchValue.value = "";
  76. toggleRowExpansionAll(true);
  77. }
  78. watch(searchValue, val => {
  79. treeRef.value!.filter(val);
  80. });
  81. defineExpose({ onTreeReset });
  82. </script>
  83. <template>
  84. <div
  85. v-loading="props.treeLoading"
  86. class="h-full bg-bg_color overflow-auto"
  87. :style="{ minHeight: `calc(100vh - 145px)` }"
  88. >
  89. <div class="flex items-center h-[34px]">
  90. <el-input
  91. v-model="searchValue"
  92. class="ml-2"
  93. size="small"
  94. placeholder="请输入部门名称"
  95. clearable
  96. >
  97. <template #suffix>
  98. <el-icon class="el-input__icon">
  99. <IconifyIconOffline
  100. v-show="searchValue.length === 0"
  101. icon="ri:search-line"
  102. />
  103. </el-icon>
  104. </template>
  105. </el-input>
  106. <el-dropdown :hide-on-click="false">
  107. <IconifyIconOffline
  108. class="w-[28px] cursor-pointer"
  109. width="18px"
  110. :icon="More2Fill"
  111. />
  112. <template #dropdown>
  113. <el-dropdown-menu>
  114. <el-dropdown-item>
  115. <el-button
  116. :class="buttonClass"
  117. link
  118. type="primary"
  119. :icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)"
  120. @click="toggleRowExpansionAll(isExpand ? false : true)"
  121. >
  122. {{ isExpand ? "折叠全部" : "展开全部" }}
  123. </el-button>
  124. </el-dropdown-item>
  125. <!-- <el-dropdown-item>
  126. <el-button
  127. :class="buttonClass"
  128. link
  129. type="primary"
  130. :icon="useRenderIcon(Reset)"
  131. @click="onTreeReset"
  132. >
  133. 重置状态
  134. </el-button>
  135. </el-dropdown-item> -->
  136. </el-dropdown-menu>
  137. </template>
  138. </el-dropdown>
  139. </div>
  140. <el-divider />
  141. <el-tree
  142. ref="treeRef"
  143. :data="props.treeData"
  144. node-key="id"
  145. size="small"
  146. :props="defaultProps"
  147. default-expand-all
  148. :expand-on-click-node="false"
  149. :filter-node-method="filterNode"
  150. @node-click="nodeClick"
  151. >
  152. <template #default="{ node, data }">
  153. <span
  154. :class="[
  155. 'pl-1',
  156. 'pr-1',
  157. 'rounded',
  158. 'flex',
  159. 'items-center',
  160. 'select-none',
  161. 'hover:text-primary',
  162. searchValue.trim().length > 0 &&
  163. node.label.includes(searchValue) &&
  164. 'text-red-500',
  165. highlightMap[node.id]?.highlight ? 'dark:text-primary' : ''
  166. ]"
  167. :style="{
  168. color: highlightMap[node.id]?.highlight
  169. ? 'var(--el-color-primary)'
  170. : '',
  171. background: highlightMap[node.id]?.highlight
  172. ? 'var(--el-color-primary-light-7)'
  173. : 'transparent'
  174. }"
  175. >
  176. <IconifyIconOffline
  177. :icon="
  178. data.type === 1
  179. ? OfficeBuilding
  180. : data.type === 2
  181. ? LocationCompany
  182. : Dept
  183. "
  184. />
  185. {{ node.label }}
  186. </span>
  187. </template>
  188. </el-tree>
  189. </div>
  190. </template>
  191. <style lang="scss" scoped>
  192. :deep(.el-divider) {
  193. margin: 0;
  194. }
  195. :deep(.el-tree) {
  196. --el-tree-node-hover-bg-color: transparent;
  197. }
  198. </style>