第十九課時: Tree組件實現文件目錄-高級實現

  1. 封裝文件目錄組件
  2. 操作目錄
  3. 多個屬性v-model替代方案
  4. 增加鉤子函數

最終呈現的效果如圖:
圖片描述

父組件folder-tree.vue:

<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:

<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函數:

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
  })
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章