实现tree树节点的新增、编辑、删除,通过输入框添加节点

208 阅读1分钟

一、环境准备

Vue3 + ant-design-vue

二、代码用例

<template>
  <div>
    <span class="tree-node-actions">
      <a-button type="text" size="small" @click="handleAddChild">
        <plus-outlined />
      </a-button>
      <a-button type="text" size="small" @click="handleEdit">
        <edit-outlined />
      </a-button>
      <a-button type="text" size="small" danger @click="handleDelete">
        <delete-outlined />
      </a-button>
    </span>
    <a-tree
      :key="treeKey"
      :tree-data="treeData"
      :expanded-keys="expandedKeys"
      v-model:selectedKeys="selectedKeys"
      @select="onSelect"
      @expand="onExpand"
    >
      <template #title="{ key, title }">
        <div style="display: flex; align-items: center">
          <span v-if="editingKey !== key">{{ title }}</span>
          <a-input
            v-else
            ref="inputRef"
            v-model:value="editingTitle"
            size="small"
            style="width: 120px"
            @blur="handleSave(key)"
            @press-enter="handleSave(key)"
          />
        </div>
      </template>
    </a-tree>
  </div>
</template>

<script setup>
  import { ref, nextTick, onMounted } from 'vue';
  import { EditOutlined } from '@ant-design/icons-vue';
  import { message } from 'ant-design-vue';

  const expandedKeys = ref([]); // 存储已展开的节点keys

  // 树数据
  const treeData = ref([
    {
      key: '0',
      title: '根节点',
      children: [
        {
          key: '1',
          title: '节点1',
          children: [
            { key: '1-1', title: '节点1-1' },
            { key: '1-2', title: '节点1-2' },
          ],
        },
        {
          key: '2',
          title: '节点2',
        },
      ],
    },
  ]);

  // 选中的节点key
  const selectedKeys = ref([]);
  const selectedKey = ref(null);
  const selectTitle = ref('');
  const treeKey = ref(0); // 用于强制刷新的key

  // 编辑相关状态
  const editingKey = ref('');
  const editingTitle = ref('');
  const inputRef = ref(null);

  // 选择节点
  const onSelect = (keys, { node }) => {
    selectedKey.value = keys[0] || null;
    selectTitle.value = node.title;
  };

  // 处理展开事件
  const onExpand = (keys) => {
    expandedKeys.value = keys;
  };

  // 添加子节点
  const handleAddChild = async () => {
    // 1. 确保父节点在expandedKeys中
    if (!expandedKeys.value.includes(selectedKey.value)) {
      expandedKeys.value = [...expandedKeys.value, selectedKey.value];
    }
    await nextTick();
    editingKey.value = `new-${Date.now()}`;
    editingTitle.value = '';

    const addNode = (nodes) => {
      for (let node of nodes) {
        if (node.key === selectedKey.value) {
          if (!node.children) {
            node.children = [];
          }
          // 先添加一个空节点
          node.children.push({
            key: editingKey.value,
            title: '',
          });
          return true;
        }
        if (node.children && node.children.length > 0) {
          if (addNode(node.children)) return true;
        }
      }
      return false;
    };

    if (addNode(treeData.value)) {
      await nextTick();
      inputRef.value?.focus();
    }
  };

  // 编辑节点
  const handleEdit = () => {
    editingKey.value = selectedKey.value;
    editingTitle.value = selectTitle.value;
    nextTick(() => {
      inputRef.value?.focus();
    });
  };

  // 保存编辑
  const handleSave = (key) => {
    if (!editingTitle.value.trim()) {
      // 如果内容为空,取消编辑或删除空节点
      if (key.startsWith('new-')) {
        removeEmptyNode(key);
      }
      editingKey.value = '';
      return;
    }

    const updateNode = (nodes) => {
      for (let node of nodes) {
        if (node.key === key) {
          node.title = editingTitle.value;
          // 如果是新节点,更新key
          if (key.startsWith('new-')) {
            node.key = `node-${Date.now()}`;
          }
          return true;
        }
        if (node.children && node.children.length > 0) {
          if (updateNode(node.children)) return true;
        }
      }
      return false;
    };

    if (updateNode(treeData.value)) {
      treeKey.value++; // 修改key值强制重新渲染
      message.success('保存成功');
    }
    editingKey.value = '';
  };

  // 删除空节点
  const removeEmptyNode = (key) => {
    const deleteNode = (nodes) => {
      for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].key === key) {
          nodes.splice(i, 1);
          return true;
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
          if (deleteNode(nodes[i].children)) return true;
        }
      }
      return false;
    };

    deleteNode(treeData.value);
  };

  // 删除节点
  const handleDelete = () => {
    if (selectedKey.value == '0') {
      message.warning('不能删除根节点');
      return;
    }
    const deleteNode = (nodes) => {
      for (let i = 0; i < nodes.length; i++) {
        if (nodes[i].key === selectedKey.value) {
          nodes.splice(i, 1);
          return true;
        }
        if (nodes[i].children && nodes[i].children.length > 0) {
          if (deleteNode(nodes[i].children)) return true;
        }
      }
      return false;
    };

    if (deleteNode(treeData.value)) {
      message.success('删除成功');
    }
  };
  // 第一次渲染展开前两级节点
  const getFirstTwoLevelKeys = (data) => {
    const keys = [];
    data.forEach((item) => {
      keys.push(item.key); // 第一级
      if (item.children) {
        item.children.forEach((child) => {
          keys.push(child.key); // 第二级
        });
      }
    });
    return keys;
  };

  onMounted(() => {
    expandedKeys.value = getFirstTwoLevelKeys(treeData.value);
  });
</script>

<style scoped>
  .tree-node-edit {
    display: flex;
    align-items: center;
    gap: 8px;
  }

  .tree-node-view {
    display: flex;
    align-items: center;
    gap: 4px;
  }

  .tree-node-view:hover {
    background-color: #f5f5f5;
  }
</style>