VUE-tree组件实现(封装文件目录组件,操作目录-render,多个属性v-moadal代替方案,增加钩子函数)

思维: 文件夹以及文件数据来自于两组数据 父级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是展开文件的id
export 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'
  })
}
原文链接:segmentfault.com

上一篇:做一个高一致性、高性能的Flutter动态渲染,真的很难么?
下一篇:【小试牛刀】手写一个form表单,实现校验功能

相关推荐

  • (vue框架)为element组件赋初始值以后无法更改值得问题

    情况描述:组件未加载时已有初始值,mounted里面加载数据,赋值,渲染以后,组件无法更改内容 data里面已经有这个表单对象的初始值但还是无法修改,之前有过一次,没有给表单绑定对象,所以赋值以后无法...

    1 年前
  • 魔方实时通信一对一音视频组件

    魔方实时通信/协作引擎(Web SDK)是一个全能力的实时云端协作引擎 魔方实时通信,请点击这个(https://www.shixincube.com/) 继上一个im聊天组件增加了发动语音,语音...

    1 年前
  • 高阶组件之属性代理

    新组件类继承子React.component类,对传入的组件进行一系列操作,从而产生一个新的组件,达到增强组件的作用 操作props 访问ref 抽取state 封装组件 ...

    1 年前
  • 高阶组件HOC - 小试牛刀

    1. 前言 老毕曾经有过一句名言,叫作“国庆七天乐,Coding最快乐~”。所以在这漫漫七天长假,手痒了怎么办?于是乎,就有了接下来的内容。。。 2. 一个中心 今天要分享的内容有关高阶组件...

    2 年前
  • 高阶组件 + New Context API = ?

    1. 前言 继上次小试牛刀(https://github.com/SmallStoneSK/Blog/issues/6)尝到高价组件的甜头之后,现已深陷其中无法自拔。。。

    2 年前
  • 高阶函数&amp;&amp;高阶组件

    高阶函数 特点: 接受函数类型的参数。 返回值是函数。 高阶函数具有可扩展性。 常见的高阶函数: 定时器 setTimeout(); setInterval() Promise(); 数组相关:...

    3 个月前
  • 高级 Angular 组件模式 (3b)

    03b Enhance Components with Directives 原文: Enhance Components with Directives(https://blog.angula...

    2 年前
  • 高级 Angular 组件模式 (3a)

    03a Communicate Between Components Using Dependency Injection 原文: Communicate Between Components ...

    2 年前
  • 高品质 React UI 组件

    (https://img.javascriptcn.com/cca319311c2ea59a2b5cdaa63b997828)(https://link.funteas.com?target=http...

    2 年前
  • 音频组件开发实践

    我的blog原文链接(https://github.com/phoebeCodeSpace/blog/blob/master/audio/vueaudio.md) 最近公司迭代的项目中,新增了音频...

    2 年前

官方社区

扫码加入 JavaScript 社区