|
@@ -1,96 +1,46 @@
|
|
|
<template>
|
|
|
- <div class="tree-container">
|
|
|
- <!-- 搜索区域 -->
|
|
|
- <div class="search-bar">
|
|
|
- <el-input
|
|
|
- v-model="searchKeyword"
|
|
|
- placeholder="快速检索"
|
|
|
- class="search-input"
|
|
|
- prefix-icon="Search"
|
|
|
- />
|
|
|
- <el-button type="primary" class="search-btn" @click="handleSearch">查询</el-button>
|
|
|
- <el-button type="default" class="reset-btn" @click="handleReset">重置</el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 树形结构区域 -->
|
|
|
- <div class="tree-wrapper">
|
|
|
- <el-tree
|
|
|
- v-model:checkedKeys="checkedKeys"
|
|
|
- :data="treeData"
|
|
|
- :props="treeProps"
|
|
|
- show-checkbox
|
|
|
- node-key="id"
|
|
|
- :filter-node-method="filterNode"
|
|
|
- ref="treeRef"
|
|
|
- class="custom-tree"
|
|
|
- :indent="16"
|
|
|
- :icon="renderIcon"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="tree-container">
|
|
|
+ <!-- 搜索区域 -->
|
|
|
+ <div class="search-bar">
|
|
|
+ <el-input v-model="searchKeyword" placeholder="快速检索" class="search-input" prefix-icon="Search" />
|
|
|
+ <el-button type="primary" class="search-btn" @click="handleSearch">查询</el-button>
|
|
|
+ <el-button type="default" class="reset-btn" @click="handleReset">重置</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 树形结构区域 -->
|
|
|
+ <div class="tree-wrapper">
|
|
|
+ <el-tree
|
|
|
+ ref="treeRef"
|
|
|
+ v-model:checkedKeys="checkedKeys"
|
|
|
+ :data="treeData"
|
|
|
+ :props="treeProps"
|
|
|
+ default-expand-all
|
|
|
+ highlight-current
|
|
|
+ show-checkbox
|
|
|
+ check-on-click-node
|
|
|
+ node-key="id"
|
|
|
+ :filter-node-method="filterNode"
|
|
|
+ @current-change="handleCheckChange"
|
|
|
+ class="custom-tree"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { ref, computed } from 'vue';
|
|
|
import { ElTree } from 'element-plus';
|
|
|
-import { CircleClose, CircleCheck } from '@element-plus/icons-vue';
|
|
|
-
|
|
|
-// 树形结构数据(模拟后端返回数据)
|
|
|
-const treeData = ref([
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- label: '交通部根节点',
|
|
|
- children: [
|
|
|
- { id: 11, label: 'XXXXXXXX机构', checked: true },
|
|
|
- {
|
|
|
- id: 12,
|
|
|
- label: 'XXXXXXXX机构',
|
|
|
- checked: true,
|
|
|
- children: [
|
|
|
- { id: 121, label: 'XXXXXXXX机构', checked: true },
|
|
|
- { id: 122, label: 'XXXXXXXX机构' },
|
|
|
- { id: 123, label: 'XXXXXXXX机构' },
|
|
|
- { id: 124, label: 'XXXXXXXX机构' },
|
|
|
- { id: 125, label: 'XXXXXXXX机构' },
|
|
|
- { id: 126, label: 'XXXXXXXX机构', checked: true },
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 13,
|
|
|
- label: 'XXXXXXXX机构',
|
|
|
- checked: true,
|
|
|
- children: [
|
|
|
- { id: 121, label: 'XXXXXXXX机构', checked: true },
|
|
|
- { id: 122, label: 'XXXXXXXX机构' },
|
|
|
- { id: 123, label: 'XXXXXXXX机构' },
|
|
|
- { id: 124, label: 'XXXXXXXX机构' },
|
|
|
- { id: 125, label: 'XXXXXXXX机构' },
|
|
|
- { id: 126, label: 'XXXXXXXX机构', checked: true },
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- id: 14,
|
|
|
- label: 'XXXXXXXX机构',
|
|
|
- checked: true,
|
|
|
- children: [
|
|
|
- { id: 121, label: 'XXXXXXXX机构', checked: true },
|
|
|
- { id: 122, label: 'XXXXXXXX机构' },
|
|
|
- { id: 123, label: 'XXXXXXXX机构' },
|
|
|
- { id: 124, label: 'XXXXXXXX机构' },
|
|
|
- { id: 125, label: 'XXXXXXXX机构' },
|
|
|
- { id: 126, label: 'XXXXXXXX机构', checked: true },
|
|
|
- ]
|
|
|
- },
|
|
|
- ]
|
|
|
- }
|
|
|
-]);
|
|
|
|
|
|
// 树形配置项
|
|
|
-const treeProps = ref({
|
|
|
- label: 'label',
|
|
|
- children: 'children',
|
|
|
- disabled: (data: any) => data.disabled, // 可自定义禁用节点
|
|
|
-});
|
|
|
+const treeProps = {
|
|
|
+ label: 'label',
|
|
|
+ children: 'children',
|
|
|
+};
|
|
|
+interface Tree {
|
|
|
+ id: number;
|
|
|
+ label: string;
|
|
|
+ children?: Tree[];
|
|
|
+}
|
|
|
|
|
|
// 搜索关键词
|
|
|
const searchKeyword = ref('');
|
|
@@ -98,112 +48,154 @@ const searchKeyword = ref('');
|
|
|
const checkedKeys = ref<(string | number)[]>([]);
|
|
|
// 树形组件实例
|
|
|
const treeRef = ref<InstanceType<typeof ElTree>>();
|
|
|
+// 树形结构数据(模拟后端返回数据)
|
|
|
+const treeData = ref<Tree[]>([
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ label: '交通部根节点',
|
|
|
+ children: [
|
|
|
+ { id: 11, label: 'XXXXXXXX机构11' },
|
|
|
+ {
|
|
|
+ id: 12,
|
|
|
+ label: 'XXXXXXXX机构12',
|
|
|
+ children: [
|
|
|
+ { id: 121, label: 'XXXXXXXX机构121' },
|
|
|
+ { id: 122, label: 'XXXXXXXX机构123' },
|
|
|
+ { id: 123, label: 'XXXXXXXX机构123' },
|
|
|
+ { id: 124, label: 'XXXXXXXX机构124' },
|
|
|
+ { id: 125, label: 'XXXXXXXX机构125' },
|
|
|
+ { id: 126, label: 'XXXXXXXX机构126' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 13,
|
|
|
+ label: 'XXXXXXXX机构13',
|
|
|
+ children: [
|
|
|
+ { id: 131, label: 'XXXXXXXX机构131' },
|
|
|
+ { id: 132, label: 'XXXXXXXX机构132' },
|
|
|
+ { id: 133, label: 'XXXXXXXX机构133' },
|
|
|
+ { id: 134, label: 'XXXXXXXX机构134' },
|
|
|
+ { id: 135, label: 'XXXXXXXX机构135' },
|
|
|
+ { id: 136, label: 'XXXXXXXX机构136' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 14,
|
|
|
+ label: 'XXXXXXXX机构14',
|
|
|
+ children: [
|
|
|
+ { id: 141, label: 'XXXXXXXX机构141' },
|
|
|
+ { id: 142, label: 'XXXXXXXX机构142' },
|
|
|
+ { id: 143, label: 'XXXXXXXX机构143' },
|
|
|
+ { id: 144, label: 'XXXXXXXX机构144' },
|
|
|
+ { id: 145, label: 'XXXXXXXX机构145' },
|
|
|
+ { id: 146, label: 'XXXXXXXX机构146' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+]);
|
|
|
|
|
|
// 过滤节点(搜索功能)
|
|
|
const filterNode = (value: string, data: any) => {
|
|
|
- if (!value) return true;
|
|
|
- return data.label.toLowerCase().includes(value.toLowerCase());
|
|
|
+ if (!value) return true;
|
|
|
+ return data.label.toLowerCase().includes(value.toLowerCase());
|
|
|
+};
|
|
|
+
|
|
|
+const handleCheckChange = () => {
|
|
|
+ // // 参数:是否只含叶子、是否含半选(不全选,也显示父级数据)
|
|
|
+ // const checkedNodes = treeRef.value!.getCheckedNodes(false, false) as any[];
|
|
|
+ // console.log('所有选中的节点:', checkedNodes);
|
|
|
+ // 如果只想拿 key 数组 参数:是否只含叶子
|
|
|
+ checkedKeys.value = treeRef.value!.getCheckedKeys(false);
|
|
|
+ console.log('选中的 keys:', checkedKeys);
|
|
|
};
|
|
|
|
|
|
// 处理搜索
|
|
|
const handleSearch = () => {
|
|
|
- treeRef.value?.filter(searchKeyword.value);
|
|
|
+ treeRef.value?.filter(searchKeyword.value);
|
|
|
};
|
|
|
|
|
|
// 处理重置
|
|
|
const handleReset = () => {
|
|
|
- searchKeyword.value = '';
|
|
|
- treeRef.value?.filter(''); // 清空搜索过滤
|
|
|
-};
|
|
|
-
|
|
|
-const renderIcon = (props: any) => {
|
|
|
- if (!props.data) return null;
|
|
|
- const isLeaf = !props.data.children || props.data.children.length === 0;
|
|
|
- // 叶子节点且未勾选:空复选框图标
|
|
|
- if (isLeaf && !props.checked) {
|
|
|
- return h(CircleClose, { class: 'tree-icon unchecked' });
|
|
|
- }
|
|
|
- // 叶子节点且勾选:绿色勾选图标
|
|
|
- if (isLeaf && props.checked) {
|
|
|
- return h(CircleCheck, { class: 'tree-icon checked' });
|
|
|
- }
|
|
|
- return null;
|
|
|
+ searchKeyword.value = '';
|
|
|
+ treeRef.value!.setCheckedKeys([], false);
|
|
|
+ treeRef.value?.filter('');
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.tree-container {
|
|
|
- width: 100%;
|
|
|
- padding: 20px;
|
|
|
- background: #fff;
|
|
|
- border-radius: 4px;
|
|
|
+ width: 100%;
|
|
|
+ padding: 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
/* 搜索区域样式 */
|
|
|
.search-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12px;
|
|
|
- margin-bottom: 20px;
|
|
|
-
|
|
|
- .search-input {
|
|
|
- flex: 1;
|
|
|
- height: 38px;
|
|
|
- width: 150px;
|
|
|
- }
|
|
|
-
|
|
|
- .search-btn, .reset-btn {
|
|
|
- min-width: 80px;
|
|
|
- height: 38px;
|
|
|
- padding: 0 16px;
|
|
|
- }
|
|
|
-
|
|
|
- .reset-btn {
|
|
|
- border: 1px solid #dcdfe6;
|
|
|
- color: #606266;
|
|
|
- margin-left: 0;
|
|
|
- }
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .search-input {
|
|
|
+ flex: 1;
|
|
|
+ height: 38px;
|
|
|
+ width: 150px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-btn,
|
|
|
+ .reset-btn {
|
|
|
+ min-width: 80px;
|
|
|
+ height: 38px;
|
|
|
+ padding: 0 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reset-btn {
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ color: #606266;
|
|
|
+ margin-left: 0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* 树形结构样式 */
|
|
|
.tree-wrapper {
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
- border-radius: 4px;
|
|
|
- max-height: 500px;
|
|
|
- overflow-y: auto;
|
|
|
- padding: 10px 0;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+ max-height: 500px;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 10px 0;
|
|
|
}
|
|
|
|
|
|
.custom-tree {
|
|
|
- --el-tree-node-content-hover-bg-color: transparent; /* 移除节点hover背景 */
|
|
|
-
|
|
|
- .el-tree-node__content {
|
|
|
- height: 36px;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- /* 复选框样式调整 */
|
|
|
- .el-checkbox .el-checkbox__inner {
|
|
|
- width: 18px;
|
|
|
- height: 18px;
|
|
|
- border-radius: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .el-checkbox__input.is-checked .el-checkbox__inner {
|
|
|
- background-color: #10b981; /* 绿色勾选背景 */
|
|
|
- border-color: #10b981;
|
|
|
- }
|
|
|
-
|
|
|
- /* 自定义图标样式 */
|
|
|
- .tree-icon {
|
|
|
- font-size: 18px;
|
|
|
- &.checked {
|
|
|
- color: #10b981; /* 绿色勾选图标 */
|
|
|
- }
|
|
|
- &.unchecked {
|
|
|
- color: #c0c6cf; /* 未勾选灰色图标 */
|
|
|
- }
|
|
|
- }
|
|
|
+ --el-tree-node-content-hover-bg-color: transparent; /* 移除节点hover背景 */
|
|
|
+
|
|
|
+ .el-tree-node__content {
|
|
|
+ height: 36px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 复选框样式调整 */
|
|
|
+ .el-checkbox .el-checkbox__inner {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-checkbox__input.is-checked .el-checkbox__inner {
|
|
|
+ background-color: #10b981; /* 绿色勾选背景 */
|
|
|
+ border-color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 自定义图标样式 */
|
|
|
+ .tree-icon {
|
|
|
+ font-size: 18px;
|
|
|
+ &.checked {
|
|
|
+ color: #10b981; /* 绿色勾选图标 */
|
|
|
+ }
|
|
|
+ &.unchecked {
|
|
|
+ color: #c0c6cf; /* 未勾选灰色图标 */
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|