最終呈現的效果如圖: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 }) }