思维:
文件夹以及文件数据来自于两组数据
父级folder-tree中:
在template中:

<folder-tree  :folder-list.sync="folderList"  :file-list.sync="fileList"  :folder-drop="folderDrop"  :file-drop="fileDrop"  :beforDelete="beforeDelete"/>

在script中:

 import { getFolderList, getFileList } from '@/api/data'   import { putFileInFolder, transferFolderToTree } from '@/lib/util'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(() => {      console.log(222)      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]})}}

子组件中:
template:

<Tree :data="folderTree" :render="renderFunc"></Tree>

script:

import { putFileInFolder, transferFolderToTree, expandSpecifiedFolder } from '@/lib/util'import clonedeep from 'clonedeep'export default {name: 'FolderTree',data () {return {  folderTree: [],  currentRenameingId: '',  currentRenameingContent: '',  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.currentRenameingId === `${data.type || 'file'}_${data.id}`    return (      <div class='tree-item'>        { data.type === 'folder' ? <icon type="ios-folder" class='folder-icon' /> : '' }        {          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: {//   type: Array,s//   default: () => {}// }// 此处如果是空数组,说明这里不需要下拉菜单,这里不用设置默认值;// 这里就不是直接判断数组是否为空,而是直接判断这里folderDrop是否为undefined,如果为undefined说明这里没有传入值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.filter(item => item.id !== data.id)  this.$emit(`update:${updateListName}`, list)  this.$nextTick(() => {    expandSpecifiedFolder(this, this.folderTree, folderId)  })},handleDropdownClick (data, name) {  if (name === 'rename') {    this.currentRenameingId = `${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.currentRenameingContent = value},updateList (list, id) {  let i = -1  let len = list.length  while (++i < len) {    let folderItem = list[i]    if (folderItem.id === id) {      folderItem.name = this.currentRenameingContent      list.splice(i, 1, folderItem)      break    }  }  return list},saveRename (data) {  const id = data.id  const folderId = data.folder_id  const type = data.type  if (type === 'folder') {    const list = this.updateList(clonedeep(this.folderList), id)    this.$emit('update:folderList', list)    this.$nextTick(() => {      expandSpecifiedFolder(this, this.folderTree, folderId)    })  } else {    const list = this.updateList(this.fileList, id)    this.$emit('update:fileList', list)    this.$nextTick(() => {      expandSpecifiedFolder(this, this.folderTree, folderId)    })  }  this.currentRenameingId = ''},delete () {  //}},mounted () {this.transData() }}

在@/lib/util中

import clonedeep from 'clonedeep'export const putFileInFolder = (folderList, fileList) => {     const folderListCloned = clonedeep(folderList)      const fileListCloned = clonedeep(fileList)      return folderListCloned.map(folderItem => {    const folderId = folderItem.id      let index = fileListCloned.length      while (--index >= 0) {          const fileItem = fileListCloned[index]          if (fileItem.folder_id === folderId) {            const file = fileListCloned.splice(index, 1)[0]         file.title = file.name        if (folderItem.children) folderItem.children.push((file))            else folderItem.children = [file]          }      }     folderItem.type = 'folder'     return folderItem     })}// 只对文件夹进行处理export const transferFolderToTree = folderList => {      if (!folderList.length) return []      const folderListCloned = clonedeep(folderList)      const handle = id => {    let arr = []    folderListCloned.forEach(folder => {      if (folder.folder_id === id) {    const children = handle(folder.id)    if (folder.children) folder.children = [].concat(folder.children, children)   else folder.children = children    folder.title = folder.name    arr.push(folder)      }    })    return arr     }      return handle(0)  }// 根据目录中id 展开指定的文件夹;folderTree代表展开文件夹树状列表,id是展开文件的idexport const expandSpecifiedFolder = (vm, folderTree, id) => {return folderTree.map(item => {if (item.type === 'folder') {  if (item.id === id) {    // item.expand = true    vm.$set(item, 'expand', true)  } else {    if (item.children && item.children.length) {      item.children = expandSpecifiedFolder(vm, item.children, id)      if (item.children.some(child => {        return child.expand === true      })) {        // item.expand = true        vm.$set(item, 'expand', true)      } else {        // item.expand = false        vm.$set(item, 'expand', false)      }    }  }}return item})}

在MOCK中

import { doCustomTimes } from '@/lib/tools'import Mock from 'mockjs'export const getFileList = () => {      const template = {       'name|5': '@cword',    'creat_time': '@datetime',    'folder_id|1-5': 0,    'id|+1': 10000      }      let arr = []      doCustomTimes(10, () => {    arr.push(Mock.mock(template))     })    return arr}export const getFolderList = () => {      const template1 = {    'name|1': '@word',    'creat_time': '@datetime',       'folder_id': 0,       'id|+1': 1      }      const template2 = {    'name|1': '@word',    'creat_time': '@datetime',    'folder_id|+1': 1,    'id|+1': 4      }      let arr = []      doCustomTimes(3, () => {    arr.push(Mock.mock(template1))      })      doCustomTimes(2, () => {    arr.push(Mock.mock(template2))      })      return arr}

在lib/tools中

// 与业务无关的工具函数export const doCustomTimes = (times, callback) => {  let i = -1  while (++i < times) {callback()      }}

在api/data中

export const getFolderList = () => {  return axios.request({url: '/getFolderList',method: 'get'  })}export const getFileList = () => {return axios.request({url: '/getFileList',method: 'get'  })}