treeSelect.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <template>
  2. <div class="tree-container">
  3. <!-- 搜索区域 -->
  4. <div class="search-bar">
  5. <el-input
  6. v-model="searchKeyword"
  7. placeholder="快速检索"
  8. class="search-input"
  9. prefix-icon="Search"
  10. />
  11. <el-button type="primary" class="search-btn" @click="handleSearch">查询</el-button>
  12. <el-button type="default" class="reset-btn" @click="handleReset">重置</el-button>
  13. </div>
  14. <!-- 树形结构区域 -->
  15. <div class="tree-wrapper">
  16. <el-tree
  17. v-model:checkedKeys="checkedKeys"
  18. :data="treeData"
  19. :props="treeProps"
  20. show-checkbox
  21. node-key="id"
  22. :filter-node-method="filterNode"
  23. ref="treeRef"
  24. class="custom-tree"
  25. :indent="16"
  26. :icon="renderIcon"
  27. />
  28. </div>
  29. </div>
  30. </template>
  31. <script setup lang="ts">
  32. import { ref, computed } from 'vue';
  33. import { ElTree } from 'element-plus';
  34. import { CircleClose, CircleCheck } from '@element-plus/icons-vue';
  35. // 树形结构数据(模拟后端返回数据)
  36. const treeData = ref([
  37. {
  38. id: 1,
  39. label: '交通部根节点',
  40. children: [
  41. { id: 11, label: 'XXXXXXXX机构', checked: true },
  42. {
  43. id: 12,
  44. label: 'XXXXXXXX机构',
  45. checked: true,
  46. children: [
  47. { id: 121, label: 'XXXXXXXX机构', checked: true },
  48. { id: 122, label: 'XXXXXXXX机构' },
  49. { id: 123, label: 'XXXXXXXX机构' },
  50. { id: 124, label: 'XXXXXXXX机构' },
  51. { id: 125, label: 'XXXXXXXX机构' },
  52. { id: 126, label: 'XXXXXXXX机构', checked: true },
  53. ]
  54. },
  55. {
  56. id: 13,
  57. label: 'XXXXXXXX机构',
  58. checked: true,
  59. children: [
  60. { id: 121, label: 'XXXXXXXX机构', checked: true },
  61. { id: 122, label: 'XXXXXXXX机构' },
  62. { id: 123, label: 'XXXXXXXX机构' },
  63. { id: 124, label: 'XXXXXXXX机构' },
  64. { id: 125, label: 'XXXXXXXX机构' },
  65. { id: 126, label: 'XXXXXXXX机构', checked: true },
  66. ]
  67. },
  68. {
  69. id: 14,
  70. label: 'XXXXXXXX机构',
  71. checked: true,
  72. children: [
  73. { id: 121, label: 'XXXXXXXX机构', checked: true },
  74. { id: 122, label: 'XXXXXXXX机构' },
  75. { id: 123, label: 'XXXXXXXX机构' },
  76. { id: 124, label: 'XXXXXXXX机构' },
  77. { id: 125, label: 'XXXXXXXX机构' },
  78. { id: 126, label: 'XXXXXXXX机构', checked: true },
  79. ]
  80. },
  81. ]
  82. }
  83. ]);
  84. // 树形配置项
  85. const treeProps = ref({
  86. label: 'label',
  87. children: 'children',
  88. disabled: (data: any) => data.disabled, // 可自定义禁用节点
  89. });
  90. // 搜索关键词
  91. const searchKeyword = ref('');
  92. // 选中的节点ID(受控于v-model:checkedKeys)
  93. const checkedKeys = ref<(string | number)[]>([]);
  94. // 树形组件实例
  95. const treeRef = ref<InstanceType<typeof ElTree>>();
  96. // 过滤节点(搜索功能)
  97. const filterNode = (value: string, data: any) => {
  98. if (!value) return true;
  99. return data.label.toLowerCase().includes(value.toLowerCase());
  100. };
  101. // 处理搜索
  102. const handleSearch = () => {
  103. treeRef.value?.filter(searchKeyword.value);
  104. };
  105. // 处理重置
  106. const handleReset = () => {
  107. searchKeyword.value = '';
  108. treeRef.value?.filter(''); // 清空搜索过滤
  109. };
  110. const renderIcon = (props: any) => {
  111. if (!props.data) return null;
  112. const isLeaf = !props.data.children || props.data.children.length === 0;
  113. // 叶子节点且未勾选:空复选框图标
  114. if (isLeaf && !props.checked) {
  115. return h(CircleClose, { class: 'tree-icon unchecked' });
  116. }
  117. // 叶子节点且勾选:绿色勾选图标
  118. if (isLeaf && props.checked) {
  119. return h(CircleCheck, { class: 'tree-icon checked' });
  120. }
  121. return null;
  122. };
  123. </script>
  124. <style scoped lang="scss">
  125. .tree-container {
  126. width: 100%;
  127. padding: 20px;
  128. background: #fff;
  129. border-radius: 4px;
  130. }
  131. /* 搜索区域样式 */
  132. .search-bar {
  133. display: flex;
  134. align-items: center;
  135. gap: 12px;
  136. margin-bottom: 20px;
  137. .search-input {
  138. flex: 1;
  139. height: 38px;
  140. width: 150px;
  141. }
  142. .search-btn, .reset-btn {
  143. min-width: 80px;
  144. height: 38px;
  145. padding: 0 16px;
  146. }
  147. .reset-btn {
  148. border: 1px solid #dcdfe6;
  149. color: #606266;
  150. margin-left: 0;
  151. }
  152. }
  153. /* 树形结构样式 */
  154. .tree-wrapper {
  155. border: 1px solid #e4e7ed;
  156. border-radius: 4px;
  157. max-height: 500px;
  158. overflow-y: auto;
  159. padding: 10px 0;
  160. }
  161. .custom-tree {
  162. --el-tree-node-content-hover-bg-color: transparent; /* 移除节点hover背景 */
  163. .el-tree-node__content {
  164. height: 36px;
  165. align-items: center;
  166. }
  167. /* 复选框样式调整 */
  168. .el-checkbox .el-checkbox__inner {
  169. width: 18px;
  170. height: 18px;
  171. border-radius: 4px;
  172. }
  173. .el-checkbox__input.is-checked .el-checkbox__inner {
  174. background-color: #10b981; /* 绿色勾选背景 */
  175. border-color: #10b981;
  176. }
  177. /* 自定义图标样式 */
  178. .tree-icon {
  179. font-size: 18px;
  180. &.checked {
  181. color: #10b981; /* 绿色勾选图标 */
  182. }
  183. &.unchecked {
  184. color: #c0c6cf; /* 未勾选灰色图标 */
  185. }
  186. }
  187. }
  188. </style>