封装Tree组件-高级实现

2020-01-01  本文已影响0人  幻影翔

实现功能

实现效果

实现文件目录的修改和删除


tree组件封装

多个属性 v-model 替代方案

父组件采用 .sync 修饰,子组件使用 $emit修改

// 父组件使用 sync 
:folder-list.sync="folderList"
:file-list.sync="fileList"

// 子组件 $emit 提交更新父组件内容
this.$emit('update:folderList', list);
this.$emit('update:fileList', list);

钩子函数

操作前等待后台操作的返回再修改前台

// 定义beforeDelete的属性
:beforeDelete="beforeDelete"

methods: {
            // 删除的钩子 模拟后台删除的动作
            beforeDelete () {
                return new Promise((resolve,reject) => {
                    setTimeout(() => {
                        let error = new Error('error');
                        if (!error) {
                            resolve();
                        } else reject(error);
                    },2000);
                })
            }
        },

完整代码

<template>
    <div class="folder-wrapper">
        <folder-tree
            :folder-list.sync="folderList"
            :file-list.sync="fileList"
            :folder-drop="folderDrop"
            :file-drop="fileDrop"
            :beforeDelete="beforeDelete"
        ></folder-tree>
    </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>
<template>
    <Tree :data="folderTree" :render="renderFunc"></Tree>
</template>
<script>
    import { putFileInFolder, transformFolderToTree, expandSpecifiedFolder } from "@/lib/util";
    import clonedeep from 'clonedeep'
    export default {
        name: 'FolderTree',
        data () {
            return {
                currentRenamingId: '',
                currentRenamingContent: '',
                folderTree: [],
                renderFunc: (h, { root, node, data }) => {
                    const dropList = data.type === 'folder' ? this.folderDrop : this.fileDrop;
                    const dropdownRender = dropList.map(item => {
                        return (<dropdown-item name={ item.name }>{ item.title }</dropdown-item>)
                    });
                    const isRenaming = this.currentRenamingId === `${data.type || 'file' }_${data.id}`;
                    /*jsx表达式*/
                    return (
                        <div class="tree-item">
                            { 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" >
                                        <icon type="md-more" size={12} />
                                    </i-button>
                                    <dropdown-menu slot="list">
                                        { dropdownRender }
                                    </dropdown-menu>
                                </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 = transformFolderToTree(putFileInFolder(this.folderList, this.fileList));
            },
            isFolder (type) {
                return type === 'folder';
            },
            // 删除操作
            handleDelete (data) {
                const isFolder = this.isFolder(data.type);
                const folderId = data.folder_id;
                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.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;
            },
            // 更新当前目录
            updateList (list, id) {
                let i = -1;
                let len = list.length;
                while (++i < len ) {
                    let item = list[i];
                    if(item.id === id) {
                        item.name = this.currentRenamingContent;
                        list.splice(i, 1, item);
                        break;
                    }
                }
                return list;
            },
            // 保存更新后的名称
            saveRename (data) {
                let id = data.id;
                const type = data.type;
                if (type === 'folder') {
                    const list = this.updateList(clonedeep(this.folderList), id);
                    // 更新父组件,有多个属性时 需要添加在父组件 .sync
                    this.$emit('update:folderList', list);
                } else {
                    const list = this.updateList(clonedeep(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;
        }
        .tree-rename-input{
            width: ~"calc(100%-80px)"
        }
    }
</style>
import clonedeep from 'clonedeep'

export const expandSpecifiedFolder = (folderTree, id) => {
    return folderTree.map(item => {
        if (item.type === 'folder') {
            if (item.id === id) {
                item.expand = true;
            } else {
                if (item.children && item.children.length) {
                    item.children = expandSpecifiedFolder(item.children, id);
                    // 判断数组中是否expand,有即为true
                    if (item.children.some(child => {
                        return child.expand === true;
                    }))  {
                        item.expand = true;
                    } else {
                        item.expand =false;
                    }
                }
            }
        }
        return item;
    })
};
上一篇 下一篇

猜你喜欢

热点阅读