You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实现VS Code风格文件树:嵌套项转Accordion功能求助

求助:实现VS Code风格侧边栏文件管理器的折叠面板功能

我正在开发类似VS Code侧边栏的文件管理器,已实现顶层文件创建、点击已有文件加号按钮创建嵌套文件的功能。现在需要将嵌套项以**Accordion(折叠面板)**形式呈现,要求:

  • 包含子文件的主标题渲染为折叠面板,且支持在其中创建更多嵌套文件
  • 子项添加嵌套文件后自动转为折叠面板,无嵌套则显示普通<p>标签

尝试实现但未成功,附上当前代码及效果截图,恳请帮助。

当前代码

import { Button } from '@/components/ui/button';
import { useEffect, useState } from 'react';

const FileTree = () => {
  const [files, setFiles] = useState([]);
  const [editing, setEditing] = useState(null);
  const [newFileName, setNewFileName] = useState('');

  const handleAddFile = (parentIndex = null) => {
    if (newFileName.trim() === '') return;

    const newFile = { name: newFileName, children: [] };

    if (parentIndex === null) {
      setFiles([...files, newFile]);
    } else {
      const newFiles = [...files];
      const addNestedFile = (file, idx) => {
        if (idx === parentIndex) {
          file.children.push(newFile);
        } else {
          file.children.forEach((child, childIdx) => {
            addNestedFile(child, `${idx}-${childIdx}`);
          });
        }
      };
      newFiles.forEach((file, idx) => addNestedFile(file, idx.toString()));
      setFiles(newFiles);
    }

    setNewFileName('');
    setEditing(null);
  };

  const handleDeleteFile = (index) => {
    setFiles(files.filter((_, i) => i !== index));
  };

  const handleDeleteNestedFile = (parentIndex, index) => {
    const newFiles = [...files];
    const deleteNestedFile = (file, idx) => {
      if (idx === parentIndex) {
        file.children = file.children.filter((_, i) => i !== index);
      } else {
        file.children.forEach((child, childIdx) => {
          deleteNestedFile(child, `${idx}-${childIdx}`);
        });
      }
    };
    newFiles.forEach((file, idx) => deleteNestedFile(file, idx.toString()));
    setFiles(newFiles);
  };
  

  const renderFileTree = (files, parentIndex = null) => {
    return files.map((file, index) => {
      const currentIndex = parentIndex === null ? index.toString() : `${parentIndex}-${index}`;
      console.log(file.children);
      return (
        
        <div key={currentIndex} style={{ marginLeft: parentIndex !== null ? 20 : 0, borderLeft: parentIndex !== null ? '2px solid #ccc' : 'none', paddingLeft: 10 }}>
          <div
            style={{ display: 'flex', alignItems: 'center' }}
          >
            <span
              onMouseEnter={() => setEditing(currentIndex)}
              className='my-2 border border-white rounded-md px-2 py-1 cursor-pointer hover:border-gray-300 hover:bg-gray-100 hover:text-gray-800'
            >
              {file.name}
            </span>
            {editing === currentIndex && (
              <>
                <button onClick={() => setEditing(`nested-${currentIndex}`)} style={{ marginLeft: 10 }}>+</button>
                <button onClick={() => parentIndex === null ? handleDeleteFile(index) : handleDeleteNestedFile(parentIndex, index)} style={{ marginLeft: 10 }}>x</button>
              </>
            )}
          </div>
         
          
          {editing === `nested-${currentIndex}` && (
            <div style={{ marginTop: 10 }}>
              <input
                value={newFileName}
                onChange={(e) => setNewFileName(e.target.value)}
                placeholder="Enter file name"
                ref={(input) => input && input.focus()}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    handleAddFile(currentIndex);
                  }
                }}
              />
              <button onClick={() => handleAddFile(currentIndex)}>✔</button>
              <button onClick={() => setEditing(null)}>✖</button>
            </div>
          )}
          {file.children.length > 0 && renderFileTree(file.children, currentIndex)}
        </div>
      );
    });
  };

  return (
    <div className='pt-56 ml-28'>
      <div>
        <Button onClick={() => setEditing('new')}>Create File</Button>
      </div>
      {editing === 'new' && (
        <div style={{ marginTop: 10 }}>
          <input
            value={newFileName}
            onChange={(e) => setNewFileName(e.target.value)}
            placeholder="Enter file name"
            ref={(input) => input && input.focus()}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                handleAddFile();
              }
            }}
          />
          <button onClick={() => handleAddFile()}>✔</button>
          <button onClick={() => setEditing(null)}>✖</button>
        </div>
      )}
      <div style={{ marginTop: 20 }}>
        {renderFileTree(files)}
      </div>
    </div>
  );
};

export default FileTree;

当前效果

文件管理器当前效果

内容的提问来源于stack exchange,提问作者Govind ganeriwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 20:52:10