<el-tree>标签问题再谈

0 阅读3分钟

回顾

回顾一下当时的问题,当时博主需要使用 el-tree 标签做一个带部门-用户层级的复选框

使用该标签开发完,出现了一个问题,当首次进入界面时,选中部门不能全选部门下的所有用户

在这里插入图片描述

当时反复鞭策了 Trae 很多轮,就是改不好,要么是这里改好了,右侧移除用户左侧的勾选框又改坏了

最终无奈,博主只要设置一进入界面,就默认展开所有部门-用户列表,也算是一种解决方案

在这里插入图片描述

相关博客

柳暗花明

今天试用了 Claude Code,配合 kimi-k2.5 模型,竟然三言两语就解决了

效果如下

在这里插入图片描述

看看人家代码是怎么写的

<template>
  <div class="container">
    <!-- 加一个按钮 显示/隐藏 树状框 -->
    <el-button type="primary" @click="toggleTreeVisibility" style="margin-bottom: 10px; margin-right: 10px;">
      {{ treeVisible ? '隐藏树状框' : '显示树状框' }}
    </el-button>

    <!-- 树状框 -->
    <div class="left-section">
      <el-tree
          ref="tree"
          v-if="treeVisible"
          :props="props"
          :data="treeData"
          show-checkbox
          node-key="userId"
          @check="handleCheck"
          :expanded-keys="expandedKeys"
          style="width: 300px;">
      </el-tree>
    </div>

    <!-- 已选择的用户框 -->
    <div class="right-section">
      <h3>已选择的用户</h3>
      <div class="selected-users">
        <el-tag
            v-for="user in selectedUsers"
            :key="user.userId"
            closable
            @close="removeUser(user.userId)"
            style="margin: 5px;">
          {{ user.userName }}
        </el-tag>
        <div v-if="selectedUsers.length === 0" class="empty-tip">
          暂无选择的用户
        </div>
      </div>
      <div class="button-group">
        <el-button @click="clearAllUsers">取消</el-button>
        <el-button type="success" @click="openDialog">确认选择</el-button>
      </div>
    </div>

    <!-- 确认对话框 -->
    <el-dialog
        title="确认选择"
        v-model="dialogVisible"
        width="500px">
      <span>确定要提交选中的用户吗?</span>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="handleCancel">取消</el-button>
          <el-button type="primary" @click="handleConfirm">确认</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data() {
    return {
      props: {
        label: function (node) {
          // 如果有 userVOList 属性,说明是部门节点,使用 departmentName
          // 如果没有 userVOList 属性,说明是用户节点,使用 userName
          return node.userVOList ? node.departmentName : node.userName;
        },
        children: 'userVOList'
      },
      treeData: [],
      dialogVisible: false,
      selectedUserIds: [],
      treeVisible: false,
      selectedUsers: [],
      defaultExpandAll: true,
      expandedKeys: []
    };
  },
  methods: {
    /**
     * 切换树状框的显示/隐藏状态
     */
    toggleTreeVisibility() {
      this.treeVisible = !this.treeVisible;
      if (this.treeVisible) {
        this.loadTreeData();
      }
    },

    /**
     * 处理节点勾选状态变化(用户点击复选框时触发)
     * 注意:Element Plus Tree 在 check-strictly=false 时会自动处理父子关联,
     * 所以我们只需要更新 selectedUsers 列表,不需要手动调用 setChecked
     */
    handleCheck(data, checkedInfo) {
      // 使用 syncSelectedUsersWithTree 统一同步选中状态
      // 因为 checkedInfo 是一个对象,不是布尔值,无法直接判断当前节点是否被选中
      this.syncSelectedUsersWithTree();

      // 如果是部门节点,展开该部门
      if (data.userVOList) {
        this.expandedKeys = [...this.expandedKeys, data.userId];
      }
    },

    /**
     * 同步 selectedUsers 与 Tree 当前勾选状态
     */
    syncSelectedUsersWithTree() {
      const tree = this.$refs.tree;
      if (tree) {
        // 获取所有选中的用户节点(叶子节点,即没有 userVOList 的节点)
        const checkedNodes = tree.getCheckedNodes(false, true);
        this.selectedUsers = checkedNodes
          .filter(node => !node.userVOList)
          .map(node => ({
            userId: node.userId,
            userName: node.userName
          }));
      }
    },

    /**
     * 从选中列表移除用户
     */
    removeUser(userId) {
      console.log('移除用户:', userId);
      // 从选中列表移除用户
      this.selectedUsers = this.selectedUsers.filter(user => user.userId !== userId);

      // 同时取消树节点的勾选状态
      const tree = this.$refs.tree;
      if (tree) {
        console.log('树组件引用:', tree);
        // 直接使用 setChecked 方法,根据 userId 取消勾选
        // 第三个参数设置为 false,不递归处理子节点
        tree.setChecked(userId, false, false);
        console.log('取消勾选操作执行完成');
      }
    },

    /**
     * 清空所有选中的用户
     */
    clearAllUsers() {
      // 清空选中用户列表
      this.selectedUsers = [];

      // 取消所有树节点的勾选状态
      const tree = this.$refs.tree;
      if (tree) {
        // 获取所有选中的节点的 key
        const checkedKeys = tree.getCheckedKeys(true);
        // 取消每个节点的勾选状态
        checkedKeys.forEach(key => {
          tree.setChecked(key, false, false);
        });
      }
    },

    /**
     * 加载部门和用户数据到树结构
     */
    loadTreeData() {
      axios.post('http://localhost:8080/departments')
          .then(response => {
            if (response.data.status === 'success') {
              this.treeData = response.data.data;
            }
          })
          .catch(error => {
            console.error('加载数据失败:', error);
          });
    },

    /**
     * 打开确认对话框
     */
    openDialog() {
      this.dialogVisible = true;
    },

    /**
     * 处理确认提交选中的用户ID
     */
    handleConfirm() {
      // 从selectedUsers中提取用户ID
      this.selectedUserIds = this.selectedUsers.map(user => user.userId);

      // 调用后端接口
      this.uploadUsers();

      // 关闭对话框
      this.dialogVisible = false;
    },

    /**
     * 处理取消提交选中的用户ID
     */
    handleCancel() {
      this.dialogVisible = false;
    },

    /**
     * 提交选中的用户ID到后端
     */
    uploadUsers() {
      // 调用后端接口
      axios.post('http://localhost:8080/uploadUsers', {
        userIds: this.selectedUserIds
      })
          .then(response => {
            console.log('接口调用成功:', response.data);
            this.$message.success('提交成功');
          })
          .catch(error => {
            console.error('接口调用失败:', error);
            this.$message.error('提交失败');
          });
    }
  }
};
</script>

<style scoped>
.container {
  display: flex;
  gap: 20px;
  padding: 20px;
}

.left-section {
  flex: 0 0 300px;
}

.right-section {
  flex: 1;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  padding: 15px;
  padding-bottom: 80px; /* 为固定按钮留出空间 */
  min-width: 300px;
  min-height: 400px;
  position: relative;
}

.right-section h3 {
  margin-top: 0;
  margin-bottom: 15px;
  font-size: 16px;
  font-weight: 500;
}

.selected-users {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.empty-tip {
  color: #909399;
  font-size: 14px;
  margin-top: 10px;
}

.button-group {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 15px;
  border-top: 1px solid #e4e7ed;
  position: absolute;
  bottom: 0;
  right: 0;
  left: 0;
  background-color: #ffffff;
}

/* 树节点样式,增加文本与复选框之间的间距 */
:deep(.el-tree-node__content) {
  padding: 4px 0;
}

/* 复选框与文本之间的间距 */
:deep(.el-checkbox__inner) {
  margin-right: 8px;
}
</style>