- 封裝文件目錄組件
- 操作目錄
- 多個屬性v-model替代方案
- 增加鉤子函數
最終呈現的效果如圖:
父組件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
})
}