最终呈现的效果如图:vue
父组件folder-tree.vue:node
<template> <div class="folder-wrapper"> <!-- 原来直接调用iview的tree组件,如今本身写个floder-tree而后在里面调用tree组件,造成本身的封装组件 --> <!-- <Tree :data="folderTree" :render="renderFunc"></Tree> --> <!-- folder-tree组件是在components调用tree,而后再加入render函数等完成本身的封装组件 --> <folder-tree :folder-list.sync="folderList" :file-list.sync="fileList" :folder-drop="folderDrop" :file-drop="fileDrop" :beforeDelete="beforeDelete" /> </div> </template> <script> import { getFolderList, getFileList } from '@/api/data' import FolderTree from '_c/folder-tree' export default { components: { FolderTree }, data () { return { folderList: [], fileList: [], folderDrop: [ { name: 'rename', title: '重命名' }, { name: 'delete', title: '删除文件夹' } ], fileDrop: [ { name: 'rename', title: '重命名' }, { name: 'delete', title: '删除文件' } ] } }, methods: { // 模拟调用的接口 beforeDelete () { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟出错 let error = new Error('error') // 若是没有出错 if (!error) { resolve() } else reject(error) }, 2000) }) } }, mounted () { Promise.all([getFolderList(), getFileList()]).then(res => { this.folderList = res[0] this.fileList = res[1] }) } } </script> <style lang="less"> .folder-wrapper{ width: 300px; } </style>
子组件folder-tree.vue:ios
<template> <Tree :data="folderTree" :render="renderFunc"></Tree> </template> <script> import { putFileInFolder, transferFolderToTree, expandSpecifiedFolder } from '@/lib/util' import clonedeep from 'clonedeep' export default { name: 'FolderTree', data () { return { folderTree: [], currentRenamingId: '', currentRenamingContent: '', /** * @description: Render 函数能够自定义节点显示内容和交互,好比添加图标,按钮等 * @param {type} 第一个参数必需要传h,Render 函数的第二个参数,包含三个字段: root <Array>:树的根节点 node <Object>:当前节点 data <Object>:当前节点的数据 * @return: 返回一个JSX */ renderFunc: (h, { root, node, data }) => { // 判断是文件夹仍是文件 const dropList = data.type === 'folder' ? this.folderDrop : this.fileDrop const dropdownRender = dropList.map(item => { return (<dropdownItem name={item.name}>{ item.title }</dropdownItem>) }) // 是否正在重命名操做,而且从新渲染组件的时候能判断出哪个元素正在更名字!!! const isRenaming = this.currentRenamingId === `${data.type || 'file'}_${data.id}` return ( <div class="tree-item"> // 判断是文件夹仍是文件,加上icon { data.type === 'folder' ? <icon type="ios-folder" color="#2d8cf0" style="margin-right: 10px;"/> : ''} // 是否进行重命名操做的渲染变化 { isRenaming ? <span> <i-input value={data.title} on-input={this.handleInput} class="tree-rename-input"></i-input> <i-button size="small" type="text" on-click={this.saveRename.bind(this, data)}><icon type="md-checkmark" /></i-button> <i-button size="small" type="text"><icon type="md-close" /></i-button> </span> : <span>{ data.title }</span> } // 是否展现右侧的下拉菜单 { dropList && !isRenaming ? <dropdown placement="right-start" on-on-click={this.handleDropdownClick.bind(this, data)}> <i-button size="small" type="text" class="tree-item-button"> <icon type="md-more" size={12}/> </i-button> <dropdownMenu slot="list"> { dropdownRender } </dropdownMenu> </dropdown> : '' } </div> ) } } }, props: { folderList: { type: Array, default: () => [] }, fileList: { type: Array, default: () => [] }, folderDrop: Array, fileDrop: Array, beforeDelete: Function }, watch: { folderList () { this.transData() }, fileList () { this.transData() } }, methods: { transData () { this.folderTree = transferFolderToTree(putFileInFolder(this.folderList, this.fileList)) }, isFolder (type) { return type === 'folder' }, handleDelete (data) { const folderId = data.folder_id // 是不是文件夹 const isFolder = this.isFolder(data.type) let updateListName = isFolder ? 'folderList' : 'fileList' let list = isFolder ? clonedeep(this.folderList) : clonedeep(this.fileList) // 返回不删除的list,该删的文件或文件夹已经不在list里了 list = list.filter(item => item.id !== data.id) // 更新父组件传入的folderList或者fileList函数!!!!! this.$emit(`update:${updateListName}`, list) // 更新tree组件视图,使删除的文件或文件夹父级是展开的 this.$nextTick(() => { expandSpecifiedFolder(this.folderTree, folderId) }) }, handleDropdownClick (data, name) { // 若是点击下拉框的更名字按钮 if (name === 'rename') { // 如今正在更名字的文件或者文件夹 this.currentRenamingId = `${data.type || 'file'}_${data.id}` // 若是点击的是删除按钮 } else if (name === 'delete') { this.$Modal.confirm({ title: '提示', content: `您肯定要删除${this.isFolder(data.type) ? '文件夹' : '文件'}《${data.title}》吗?`, onOk: () => { // 模拟调用的接口: 若是传进来了就判断成功与失败不然就直接删除 this.beforeDelete ? this.beforeDelete().then(() => { this.handleDelete(data) }).catch(() => { this.$Message.error('删除失败') }) : this.handleDelete(data) } }) } }, handleInput (value) { this.currentRenamingContent = value }, /** * @description: 更新传入的list数组 * @param {array, num} list 须要更新的数组,id 须要更新的元素id * @return: 更新完的list数组 */ updateList (list, id) { let i = -1 let len = list.length // 循环list数组 while (++i < len) { let folderItem = list[i] // 若是传入的id和list里的元素id相同,说明更改了这个元素 if (folderItem.id === id) { // 知足条件的元素name值为改变的值,而后放到更新一下list folderItem.name = this.currentRenamingContent list.splice(i, 1, folderItem) break } } return list }, saveRename (data) { const id = data.id const type = data.type if (type === 'folder') { const list = this.updateList(clonedeep(this.folderList), id) // 更新父组件传入的值 this.$emit('update:folderList', list) } else { const list = this.updateList(this.fileList, id) this.$emit('update:fileList', list) } this.currentRenamingId = '' } }, mounted () { this.transData() } } </script> <style lang="less"> .tree-item{ display: inline-block; width: ~"calc(100% - 50px)"; height: 30px; line-height: 30px; & > .ivu-dropdown{ float: right; } ul.ivu-dropdown-menu{ padding-left: 0; } li.ivu-dropdown-item{ margin: 0; padding: 7px 16px; } .tree-rename-input{ width: ~"calc(100% - 80px)"; } } </style>
util.js中的expandSpecifiedFolder函数:api
export const expandSpecifiedFolder = (folderTree, id) => { return folderTree.map(item => { if (item.type === 'folder') { if (item.id === id) { item.expand = true } else { // 若是文件夹有children的话 if (item.children && item.children.length) { // 递归调用 item.children = expandSpecifiedFolder(item.children, id) // 若是item的子集children里有一个child的expand为true,那么item的expand就为true if (item.children.some(child => { return child.expand === true })) { item.expand = true } else { item.expand = false } } } } return item }) }