element目录树组件el-tree使用相关笔记

文章详细介绍了Vue中el-tree组件的配置、懒加载数据处理、递归遍历、搜索功能、节点高亮、图标添加、可编辑树以及点击事件的处理。

默认配置

            <el-tree
              ref="tree"
              highlight-current // 高亮当前选中节点
              accordion // 只打开一个同级树节点展开
              :data="treeData" 
              :expand-on-click-node="false" // 单击节点是否展开(懒加载模式下无效)
              :props="treeDefaultProps" // 映射属性值
              node-key="id" // 标识属性
              :default-expanded-keys="['-1']"  // 默认展开的节点
              @node-click="handleNodeClick" 
              :filter-node-method="filterNode" // 对树节点进行筛选时执行的方法,返回 true则显示
              :check-on-click-node="true" // 点击节点时选中
            >
data() {
   
   
    return {
   
    
	   	treeData:[
                id: '-1',
                label: '全部',
                leaf: false,
                children: [
				{
   
   
				...
				},
				{
   
   
				...
				}
			],
		],
		treeDefaultProps:{
   
   
        children: 'children',
        label: 'label',
        isLeaf: 'leaf',
      },
    }        
}

懒加载

   <el-tree
     :load="queryTreeData"
     lazy
   >
    // 获取树目录
    queryTreeData(node, resolve) {
   
   
      const {
   
    level } = node
      // nodeId 为 -1代表第一次请求
      let nodeId = level == 0 ? '-1' : node.data.id
      const params = {
   
   
        displayTable: true,
        parentId: nodeId,
      }
      api
        .queryDirectoryLazyLoad(params)
        .then((res) => {
   
   
          const nodes = res.map((item) => ({
   
   
            id: item.id,
            label: item.name,
            leaf: !item.hasChildren,
          }))
          // 初始化请求
          if (level == 0) {
   
   
            resolve([
              {
   
   
                id: '-1',
                label: '全部',
                leaf: false,
                children: nodes, // 非必要
              },
            ])
          } else {
   
   
            resolve(nodes)
          }
        })
        .then(() => {
   
   
          this.$refs.tree.setCurrentKey(this.directoryId)
        })
        .catch((error) => {
   
   
          console.log(error)
        })
    },

每一级分页懒加载

(待补充)

递归处理数据

递归遍历树级结构,进行字段映射

    mapTree(data) {
   
   
      const haveChildren =
        Array.isArray(data.childNode) && data.childNode.length
      return {
   
   
        label: data.nodeName,
        id: data.nodeId,
        children: haveChildren
          ? data.childNode.map((i) => this
修改代码,添加文件时要求弹出文件名输入框。 <template> <div class="markdown-editor-container"> <!-- 主体布局 --> <el-container class="main-layout"> <!-- 左侧文件树 --> <el-aside width="300px" class="file-tree-container"> <div class="tree-header"> <div> <span style="margin: 10px;">知识库</span> </div> <div style="align-items: right;"> <el-button text @click="handleRefreshFileTree"> <el-icon> <Refresh /> </el-icon> </el-button> <el-button text @click="handleCreateNewFile"> <el-icon> <DocumentAdd /> </el-icon> </el-button> <el-button text @click="handlecreateNewFolder"> <el-icon> <FolderAdd /> </el-icon> </el-button> </div> </div> <el-scrollbar max-height="calc(100vh - 120px)"> <el-tree ref="treeRef" :data="fileTreeData" :props="treeProps" node-key="id" :default-expanded-keys="['root']" :default-selected-keys="selectedFileId ? [selectedFileId] : []" @node-click="handleTreeNodeClick" @node-contextmenu="handleRightClick" draggable :allow-drag="allowDrag" :allow-drop="allowDrop" highlight-current> <template #default="{ node, data }"> <div class="tree-node-content"> <el-icon v-if="data.is_dir" class="node-icon"> <Folder /> </el-icon> <el-icon v-else class="node-icon"> <Document /> </el-icon> <span class="node-label">{{ data.name }}</span> <div class="node-actions" v-if="data.id !== 'root'"> <el-button v-if="!data.is_dir" type="text" size="small" icon="Edit" @click.stop="renameFile(data)" /> <el-button type="text" size="small" icon="Delete" @click.stop="deleteNode(data)" /> </div> </div> </template> </el-tree> </el-scrollbar> </el-aside> <!-- 右侧编辑器 --> <el-main class="editor-container"> <div v-if="selectedFile" class="editor-header"> <h3>{{ selectedFile.name }}</h3> <div class="editor-actions"> <el-button size="small" @click="previewMode = !previewMode"> {{ previewMode ? '编辑' : '预览' }} </el-button> </div> </div> <div v-else class="empty-editor"> <el-empty description="请选择一个文件进行编辑" /> </div> <div class="editor-wrapper"> <MdEditor v-model="mdContent" :preview="previewMode" previewTheme="default" theme="light" @on-save="saveCurrentFile" /> </div> </el-main> </el-container> <!-- 右键菜单 --> <el-popover ref="contextMenuRef" placement="bottom-start" trigger="manual" :visible="contextMenuVisible" :virtual-ref="contextMenuTarget" virtual-triggering> <div class="context-menu"> <el-button v-if="contextMenuData && contextMenuData.is_dir" type="text" size="small" @click="handleCreateNewFileInFolder(contextMenuData)"> <el-icon> <DocumentAdd /> </el-icon> 新建文件 </el-button> <el-button v-if="contextMenuData" type="text" size="small" @click="renameFile(contextMenuData)"> <el-icon> <Edit /> </el-icon> 重命名 </el-button> <el-button v-if="contextMenuData" type="text" size="small" @click="deleteNode(contextMenuData)"> <el-icon> <Delete /> </el-icon> 删除 </el-button> </div> </el-popover> <!-- 文件/文件夹重命名对话框 --> <el-dialog v-model="renameDialogVisible" title="重命名" width="400px"> <el-input v-model="renameValue" placeholder="请输入新名称" @keyup.enter="confirmRename" /> <template #footer> <span class="dialog-footer"> <el-button @click="renameDialogVisible = false">取消</el-button> <el-button type="primary" @click="confirmRename">确定</el-button> </span> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted, nextTick } from 'vue' import { ElMessage, ElMessageBox, ElTree } from 'element-plus' import { Folder, Document, FolderAdd, DocumentAdd, Edit, Delete, Refresh } from '@element-plus/icons-vue' import { MdEditor } from 'md-editor-v3'; import 'md-editor-v3/lib/style.css'; import { kbnote_addfile, kbnote_filetree, kbnote_getfile, kbnote_updatefile } from '@/api/kbnote'; // 响应式数据 const treeRef = ref() const contextMenuRef = ref() const contextMenuVisible = ref(false) const contextMenuTarget = ref(null) const contextMenuData = ref(null) const renameDialogVisible = ref(false) const renameValue = ref('') const selectedFileId = ref(null) const selectedFile = ref(null) const previewMode = ref(false) const darkMode = ref(false) const mdContent = ref('') // 文件树数据 const fileTreeData = ref([]) // 树节点属性配置 const treeProps = { children: 'children', label: 'name' } // 允许拖拽的节点 const allowDrag = (node) => { return node.data.id !== 'root' } // 允许放置的节点 const allowDrop = (draggingNode, dropNode, type) => { // 不允许将节点拖拽到根节点下 if (dropNode.data.id === 'root' && type === 'inner') { return !draggingNode.data.is_dir } // 只允许在文件夹内放置 if (type === 'inner') { return dropNode.data.is_dir } return true } const handleRefreshFileTree = () => { kbnote_filetree().then((res) => { console.log(res) fileTreeData.value = res.data.filetree const rootNode = { id: 'root', name: '根目录', path: '/', is_dir: true, children: fileTreeData.value } fileTreeData.value = [rootNode] }).catch((err) => { console.error('Error loading file tree:', err) }) } // 处理树节点点击 const handleTreeNodeClick = (data) => { if (!data.is_dir) { selectedFileId.value = data.id selectedFile.value = data // 重置预览模式 previewMode.value = false console.log(data) kbnote_getfile({ path: data.path }).then((res) => { mdContent.value = res.data.body console.log(res) }).catch((err) => { console.error('Error loading file:', err) }) } } // 处理右键点击 const handleRightClick = (event, data, node, e) => { event.preventDefault() contextMenuData.value = data contextMenuTarget.value = e.target contextMenuVisible.value = true } // 保存当前文件 const saveCurrentFile = () => { if (selectedFile.value) { kbnote_updatefile({ path: selectedFile.value.path, body: mdContent.value }).then((res) => { console.log(res) ElMessage.success('文件已保存') }).catch((err) => { console.error('Error saving file:', err) }) } else { ElMessage.warning('请先选择一个文件') } } // 创建新文件 const handleCreateNewFile = () => { kbnote_addfile({ path: '', filename: 'file' + Date.now() + '.md', body: '# 新文件\n\n开始编辑...' }).then((res) => { console.log(res) ElMessage.success('文件创建成功') }).catch((err) => { console.error('Error creating file:', err) }) } // 在指定文件夹内创建新文件 const handleCreateNewFileInFolder = (folderData) => { const newFile = { id: `file${Date.now()}`, name: '新文件.md', path: `${folderData.path}/file${Date.now()}.md`, is_dir: false, content: '# 新文件\n\n开始编辑...' } // 选中新建的文件 selectedFileId.value = newFile.id selectedFile.value = newFile previewMode.value = false ElMessage.success('文件创建成功') contextMenuVisible.value = false } // 创建新文件夹 const handlecreateNewFolder = () => { const newFolder = { id: `folder${Date.now()}`, name: '新文件夹', path: `/new/path/folder${Date.now()}`, is_dir: true, children: [] } // 添加到根目录 const rootNode = fileTreeData.value[0] if (!rootNode.children) { rootNode.children = [] } rootNode.children.push(newFolder) ElMessage.success('文件夹创建成功') } // 重命名文件/文件夹 const renameFile = (data) => { if (!data) return renameValue.value = data.name contextMenuVisible.value = false renameDialogVisible.value = true // 临时存储当前操作的数据 contextMenuData.value = data } // 确认重命名 const confirmRename = () => { if (!renameValue.value.trim()) { ElMessage.error('名称不能为空') return } // 找到对应的节点并更新 const updateNodeLabel = (nodes) => { for (let node of nodes) { if (node.id === contextMenuData.value.id) { node.name = renameValue.value break } if (node.children) { updateNodeLabel(node.children) } } } updateNodeLabel(fileTreeData.value) renameDialogVisible.value = false ElMessage.success('重命名成功') } // 删除节点 const deleteNode = async (data) => { if (!data) return try { await ElMessageBox.confirm( `确认删除 "${data.name}" 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' } ) // 找到父节点并删除 const deleteFromParent = (nodes, targetId) => { for (let i = 0; i < nodes.length; i++) { if (nodes[i].id === targetId) { nodes.splice(i, 1) return true } if (nodes[i].children) { if (deleteFromParent(nodes[i].children, targetId)) { return true } } } return false } deleteFromParent(fileTreeData.value, data.id) // 如果删除的是当前选中的文件,清空编辑器 if (selectedFileId.value === data.id) { selectedFileId.value = null selectedFile.value = null } contextMenuVisible.value = false ElMessage.success('删除成功') } catch { // 用户取消删除 } } // 页面加载时的初始化 onMounted(() => { handleRefreshFileTree() }) </script> <style scoped> .markdown-editor-container { height: calc(100vh - 60px); display: flex; flex-direction: column; background-color: #f5f7fa; } .top-toolbar { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: white; border-bottom: 1px solid #e4e7ed; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 100; } .top-toolbar h1 { margin: 0; color: #303133; } .toolbar-actions { display: flex; gap: 10px; } .main-layout { flex: 1; overflow: hidden; } .file-tree-container { background: white; border-right: 1px solid #e4e7ed; display: flex; flex-direction: column; } .tree-header { padding: 2px; border-bottom: 1px solid #e4e7ed; display: flex; justify-content: space-between; align-items: center; font-weight: bold; color: #606266; } .editor-container { padding: 0; display: flex; flex-direction: column; } .editor-header { padding: 15px 20px; border-bottom: 1px solid #e4e7ed; display: flex; justify-content: space-between; align-items: center; background: white; } .editor-header h3 { margin: 0; color: #303133; } .editor-actions { display: flex; gap: 10px; } .editor-wrapper { flex: 1; overflow: hidden; } .empty-editor { display: flex; align-items: center; justify-content: center; height: 100%; background: white; } .tree-node-content { flex: 1; display: flex; align-items: center; font-size: 14px; padding-right: 8px; } .node-icon { margin-right: 8px; color: #409eff; } .node-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .node-actions { display: none; margin-left: 8px; } :deep(.el-tree-node__content:hover) .node-actions { display: flex; } .context-menu { display: flex; flex-direction: column; gap: 4px; } .context-menu .el-button { justify-content: flex-start; padding: 8px 12px; } .context-menu .el-button .el-icon { margin-right: 8px; } :deep(.md-editor) { height: calc(100vh - 10px); } :deep(.el-tree) { background: transparent; } :deep(.el-tree-node.is-current > .el-tree-node__content) { background-color: #f0f9ff; color: #409eff; } </style>
最新发布
11-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值