数据分成两部分返回,文件夹列表以及文件列表,因为二者有包含关系,需要前端根据返回的数据渲染成树形结构数据。
安装express
npm init -y
npm install express
parent数据代表文件夹数据,child的数据代表文件
let express = require('express');
let app = express();
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
if(req.method === 'OPTIONS'){
return res.send();
}
next();
});
app.get('/getTreeList',function(req,res){
res.json({
code:0,
parent:[
{name:'文件夹1',pid:0,id:1},
{name:'文件夹2',pid:0,id:2},
{name:'文件夹3',pid:0,id:3},
{name:'文件夹1-1',pid:1,id:4},
{name:'文件夹2-1',pid:2,id:5},
],
child:[
{name:'文件1',pid:1,id:10001},
{name:'文件2',pid:1,id:10002},
{name:'文件2-1',pid:2,id:10003},
{name:'文件2-2',pid:2,id:10004},
{name:'文件1-1-1',pid:4,id:10005},
{name:'文件2-1-1',pid:5,id:10006}
]
});
});
app.listen(3000);
import Vue from 'vue';
// 引入组件库行
import ElementUI from 'element-ui';
// 引入组件库样式
import 'element-ui/lib/theme-chalk/index.css';
// 引入App根组件
import App from './App.vue';
// 使用插件
Vue.use(ElementUI);
export default new Vue({
el:'#app',
render:h=>h(App)
});
创建api.js 到出获取列表方法
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000';
export const getTreeList = async () =>{
return axios.get('/getTreeList');
}
对elememnt-ui树组件进行二次封装,封装TreeComponent组件
<template>
<div>
<TreeComponent :data="treeList"></TreeComponent>
</div>
</template>
<script>
import {getTreeList} from './api.js';
import TreeComponent from './TreeComponent.vue';
export default {
data(){
return {
treeList:[]
}
},
async mounted(){
let {data} = await getTreeList();
// 增加标示 如果是文件夹 增加type标示
let parent = data.parent.map(item=>(item.type="child",item));
this.treeList = [...parent,...data.child];
},
components:{
TreeComponent
}
}
</script>
处理数据时不能对父组件传递的数据直接更改,所以操作前需要进行数据的拷贝
npm install lodash
<template>
<el-tree
:data="treeList">
</el-tree>
</template>
<script>
import _ from 'lodash'
export default {
props:{
// 树的信息列表
data:{
type:Array,
default:()=>[]
}
},
data(){
return {
treeList:[] // 格式化后的树的数据结构
}
},
methods: {
processData(){
// 如果没有数据不进行任何处理
if(this.data.length != 0){
// 创造一个id的映射表,通过映射表创造关系,多数据操作时不要直接修改原数据
let cloneList = _.cloneDeep(this.data);
let mapList = cloneList.reduce((memo,current)=>{
memo[current['id']] = current;
return memo;
},{});
// 去列表中找 进行分类,最后返回数组结构
this.treeList = cloneList.reduce((result,current)=>{
current.label = current.name;// 树的结构必须要有label属性
let parent = mapList[current.pid]; // 拿到当前项的父id去列表中查找,如果找到说明是儿子,就将它放到父亲的children属性中
if(parent){
parent.children? parent.children.push(current): parent.children = [current];
}else if(current.pid === 0){ // 说明这个是根把根放进到result中
result.push(current);
}
return result
},[]);
}
}
},
watch: {
data:{
handler(){ // 监控data的变化,如果数据有更新重新处理数据
this.processData();
},
immediate:true // 默认上来就调用一次
}
},
}
</script>
文件夹添加文件夹标识,文件添加文件标识
通过render函数扩展树组件
<el-tree
:data="treeList"
:render-content="renderFn"
:expand-on-click-node="false"
default-expand-all
>
</el-tree>
isParent(type){
return type === 'parent';
},
renderFn(h, { node, data, store }){
let parent = this.isParent(data.type);
return (<div>
{parent?
node.expanded?
<i class="el-icon-folder-opened icon"></i>:
<i class="el-icon-folder icon"></i>:
<i class="el-icon-document icon"></i>}
{data.label}
</div>)
},
列表数据应该从外部传入,对文件夹和文件实现不同的操作
<TreeComponent
:data="treeList"
:rootList="rootList"
:childList="childList"
></TreeComponent>
rootList:[
{name:'rename',text:'修改文件夹名字'},
{name:'delete',text:'删除文件夹'}
],
childList:[
{name:'rename',text:'修改文件名字'},
{name:'delete',text:'删除文件'}
]
实现下拉菜单
let list = parent? this.rootList:this.childList;
<el-dropdown
placement="bottom-start"
trigger="click"
on-command={this.handleCommand}
>
<i class="el-icon-arrow-down el-icon--right"></i>
<el-dropdown-menu slot="dropdown">
{list.map(item=>(
<el-dropdown-item command={item.name}>{item.text}</el-dropdown-item>
))}
</el-dropdown-menu>
</el-dropdown>
handleCommand(name){
if(name === 'rename'){
// 修改文件名
}else{
// 删除文件
}
}
if(name === 'rename'){
this.currentId = data.id;
this.currentContent = data.label;
}
{this.currentId === data.id?
<span>
<el-input
value={this.currentContent}
on-input={this.handleChange}
></el-input>
<el-button type="text">
<i class="el-icon-close"></i>
</el-button>
<el-button type="text">
<i class="el-icon-check"></i>
</el-button>
</span>
:data.label
}
使用.sync 同步修改后的数据
<el-button type="text" on-click={this.check.bind(this,data)}>
<i class="el-icon-check"></i>
</el-button>
check(data){
let list = _.cloneDeep(this.data);
list = list.map(item=>{
if(item.id === data.id){
item.name = this.currentContent;
}
return item;
});
this.$emit('update:data',list);
this.currentId = '';
}
<el-button type="text" on-click={this.close}>
<i class="el-icon-close"></i>
</el-button>
close(){
this.currentId = '';
}
this.$confirm(`确认删除 ${data.type==='parent'?'文件夹':'文件'}吗`,"确认?",{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(()=>{
this.handleDelete(data); // 删除文件
this.$message({
type:'success',
message:'删除成功'
})
}).catch(err=>{
this.$message({
type:'info',
message:'已取消删除'
})
});
handleDelete(data){
let list = _.cloneDeep(this.data);
list = list.filter(item=> item.id !== data.id);
this.$emit('update:data',list);
this.currentId = '';
}
如果用户传递了delete方法,调用成功后在更新页面数据
<TreeComponent
:data.sync="treeList"
:rootList="rootList"
:childList="childList"
:delete="deleteFn"
></TreeComponent>
this.delete && this.delete(data.id).then(()=>{
this.handleDelete(data);
this.$message({
type:'success',
message:'删除成功'
})
})