基础功能同el-tree
<template>
<div class="baseTree">
<el-input v-if="isShowFilter" size="small" :placeholder="placeholder" v-model="filterText">
el-input>
<el-tree ref="baseTree"
:data="treeList"
:node-key="treeProps.id"
:props="treeProps"
:highlight-current="highlight"
:accordion="accordion"
:default-expand-all="expand"
:default-expanded-keys="expandedKeys"
:auto-expand-parent="expandParent"
:expand-on-click-node="expandNode"
:show-checkbox="multiple"
:check-strictly="checkStrictly"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
@check="handleCheck"
@check-change="handleCheckChange"
@node-contextmenu="handleNodeContextMenu">
el-tree>
div>
template>
<script>
export default {
props: {
// treeList: {
// type: Array,
// default () {
// return [];
// }
// },
// 默认树形结构配置项
treeProps: {
type: Object,
default() {
return {
id: 'id', // Number类型:树组件ID(node-key)
label: 'label', // String类型 : 树组件显示名称
pid: 'parentId', // Number类型:父级ID
children: 'children' // String类型:子节点
};
}
},
// 自动收起
accordion:{
type:Boolean,
default:() => {
return false }
},
// 是否展开所有节点,默认展开
expand: {
type: Boolean,
default() {
return true;
}
},
// 展开子节点的时候是否自动展开父节点 默认值为 true
expandParent:{
type: Boolean,
default() {
return true;
}
},
// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
expandNode: {
type: Boolean,
default() {
return true;
}
},
// 默认展开的节点的 key 的数组
expandedKeys:{
type: Array,
default() {
return [];
}
},
// 是否高亮当前选中节点,默认值是 true
highlight: {
type: Boolean,
default() {
return true;
}
},
// 是否可多选,默认单选
multiple: {
type: Boolean,
default() {
return false;
}
},
// 显示复选框情况下,是否严格遵循父子不互相关联
checkStrictly: {
type: Boolean,
default() {
return false;
}
},
// 图标url
iconUrl: {
type: String,
default() {
return '';
}
},
placeholder: {
type: String,
default: () => {
return '检索关键字';
}
},
// 是否需要关键字过滤
isShowFilter: {
type: Boolean,
default: () => {
return false;
}
}
},
data() {
return {
filterText: '',
visible: false
};
},
methods: {
/**
* @description: 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
* @param {*} value
* @param {*} data
* @return {*}
*/
filterNode(value, data) {
if(!value) return true;
return data[this.treeProps.label].indexOf(value) !== -1;
},
/**
* @description: 节点被展开时触发的事件
* @param {*} data 该节点所对应的数据对象
* @param {*} node 节点对应的Node对象
* @param {*} vueComponent 节点组件本身
* @return {*}
*/
handleNodeExpand(data, node, vueComponent){
this.$emit('handleNodeExpand', data);
},
/**
* @description: 节点被关闭时触发的事件
* @param {*} data 该节点所对应的数据对象
* @param {*} node 节点对应的Node对象
* @param {*} vueComponent 节点组件本身
* @return {*}
*/
handleNodeCollapse(data, node, vueComponent){
this.$emit('handleNodeCollapse', data);
},
/**
* @description: [事件] - 节点被点击时的回调
* @param {*} data 该节点所对应的数据对象
* @param {*} node 节点对应的Node对象
* @param {*} vueComponent 节点组件本身
* @return {*}
*/
handleNodeClick(data, node, vueComponent) {
this.$emit('handleNodeClick', data);
},
/**
* @description: 事件 - 当某一节点被鼠标右键点击时会触发该事件
* @param {*} event
* @param {*} data 传递给 data 属性的数组中该节点所对应的对象
* @param {*} node 节点对应的Node对象
* @param {*} vueComponent 节点组件本身
* @return {*}
*/
handleNodeContextMenu(event, data, node, vueComponent){
this.$emit('handleNodeContextMenu', event, data);
},
/**
* @description: [事件] - 当复选框被点击的时候触发
* @param {*} checkedNodes 该节点所对应的对象
* @param {*} checkedKeys 树目前的选中状态对象
* @param {*} halfCheckedNodes
* @param {*} halfCheckedKeys
* @return {*}
*/
handleCheck(node, checkedData) {
// console.log('handleCheck: ', node, checkedData)
this.$emit('handleCheck', node, checkedData);
},
/**
* @description: 事件 - 节点选中状态发生变化时的回调
* @param {*}
* @return {*}
*/
handleCheckChange(data, checked) {
let currentNode = this.$refs.baseTree.getNode(data);
// console.log('handleCheckChange: ', data, checked)
if(this.checkStrictly){
// 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联
if(checked) {
// 选中 子节点只要被选中父节点就被选中
this.parentNodeChange(currentNode);
} else {
// 未选中 处理子节点全部未选中
this.childNodeChange(currentNode);
}
}
this.$emit('handleCheckChange', data, checked);
},
// ------------------------------------------------------------------------
/**
* @description: 获取当前被选中节点的 key,使用此方法必须设置 node-key 属性,若没有节点被选中则返回 null
* @param {*}
* @return {*}
*/
getCurrentKey: function () {
return this.$refs.baseTree.getCurrentKey();
},
/**
* @description: 获取当前被选中节点的 data,若没有节点被选中则返回 null
* @param {*}
* @return {*}
*/
getCurrentNode: function () {
return this.$refs.baseTree.getCurrentNode();
},
/**
* @description: 通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
* @param {*} key 待被选节点的 key,若为 null 则取消当前高亮的节点
* @return {*}
*/
setCurrentKey:function(key){
// $nextTick 是确保DOM渲染结束之后执行的
this.$nextTick(() => {
this.$refs.baseTree.setCurrentKey(key);
})
},
setCurrentNode:function(node){
// $nextTick 是确保DOM渲染结束之后执行的
this.$nextTick(() => {
this.$refs.baseTree.setCurrentNode(node);
})
},
/**
* @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
* @param {*}
* @return {*}
*/
getCheckedKeys: function () {
return this.$refs.baseTree.getCheckedKeys();
},
/**
* @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
* @param {*}
* @return {*}
*/
getCheckedNodes: function () {
return this.$refs.baseTree.getCheckedNodes();
},
/**
* @description: 通过 keys 设置目前勾选的节点,使用此方法必须设置 node-key 属性
* @param {*} keys 勾选节点的 key 的数组
* @param {*} leafOnly boolean 类型的参数,若为 true 则仅设置叶子节点的选中状态,默认值为 false
* @return {*}
*/
setCheckedKeys: function(keys){
// if(!keys)keys = [];
const leafOnly = false;
this.$nextTick(() => {
this.$refs.baseTree.setCheckedKeys(keys, leafOnly);
})
},
/**
* @description: 通过 key / data 设置某个节点的勾选状态,使用此方法必须设置 node-key 属性
* @param {*} val 勾选节点的 key 或者 data
* @param {*} checked boolean 类型,节点是否选中
* @param {*} deep boolean 类型,是否设置子节点 ,默认为 false
* @return {*}
*/
setChecked: function(val, checked, deep){
this.$refs.baseTree.setChecked(val, checked);
},
// --------------------------------------------------------------------------
// 统一处理子节点为不选中
childNodeChange (node) {
for(let i = 0; i < node.childNodes.length; i++) {
node.childNodes[i].checked = false;
this.childNodeChange(node.childNodes[i]);
}
},
// 统一处理父节点为选中
parentNodeChange (node) {
if(node.parent.key !== undefined) {
node.parent.checked = true;
this.parentNodeChange(node.parent);
}
}
},
computed: {
/**
* @description: 树形结构数据(非标准的转换为标准结构)
* @param {*}
* @return {*}
*/
treeList() {
return this.$attrs.treeList || [];
}
},
watch: {
filterText(val) {
this.$refs.baseTree.filter(val);
}
}
};
script>
<style lang="scss">
// 字体和大小
.custom-tree-node {
font-family:"Microsoft YaHei";
font-size: $size14;
position: relative;
}
// 选中状态背景色
.baseTree .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: #F5F7FA !important;
}
// 原生el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条
.baseTree .el-tree >.el-tree-node {
display: inline-block;
min-width: 100%;
}
style>
<template>
<div class="treeDemo">
<el-container>
<el-main>
<baseTree
ref="tree"
:treeProps="props"
:treeList="treeList"
:accordion="isAccordion"
:expandNode="false"
:isShowFilter="true"
@handleNodeClick="handleNodeClick"
@handleNodeContextMenu="handleNodeContextMenu"/>
el-main>
<el-main>
<span>父子不互相关联:check-strictly=truespan>
<br />
<br />
<baseTree
:multiple="true"
:collapse="false"
:checkStrictly="true"
:treeProps="props"
:treeList="treeList"
:accordion="isAccordion"
:expandNode="false"
:isShowFilter="true"
@handleCheck="handleCheck"
@handleCheckChange="handleCheckChange"/>
el-main>
<el-main>
<span>父子互相关联:check-strictly=falsespan>
<br />
<br />
<baseTree
:multiple="true"
:collapse="false"
:treeProps="props"
:treeList="treeList"
:accordion="isAccordion"
:expandNode="false"
:isShowFilter="true"
/>
el-main>
<div v-show="menuVisible">
<ul id="menu" class="menu" @mouseleave="foo">
<li class="menu_item" >新增li>
<li class="menu_item" >删除li>
<li class="menu_item" >上移li>
<li class="menu_item" >下移li>
ul>
div>
el-container>
div>
template>
<script>
import baseTree from '_c/basics/base-tree';
export default {
name:'demo',
components: {
baseTree
},
data() {
return {
props:{
// 配置项(必选)
id: 'id',
label: 'name',
pid: 'parentId',
children: 'children'
// disabled:true
},
// 数组
list: [
{
id:1, parentId:0, name:'一级菜单A', rank:1},
{
id:2, parentId:0, name:'一级菜单B', rank:1},
{
id:3, parentId:0, name:'一级菜单C', rank:1},
{
id:4, parentId:1, name:'二级菜单A-A', rank:2},
{
id:5, parentId:1, name:'二级菜单A-B', rank:2},
{
id:6, parentId:2, name:'二级菜单B-A', rank:2},
{
id:7, parentId:4, name:'三级菜单A-A-A', rank:3},
{
id:15, parentId:0, name:'一级菜单C', rank:1},
{
id:16, parentId:0, name:'一级菜单C', rank:1},
{
id:17, parentId:0, name:'一级菜单C', rank:1},
{
id:18, parentId:0, name:'一级菜单C', rank:1}
],
treeList:[],
isClearable:true, // 可清空(可选)
isAccordion:false, // 可收起(可选)
menuVisible: false
}
},
created(){
this.initData();
},
methods: {
initData(){
this.treeList = this.listToTree(this.list, this.props);
console.log(this.treeList);
},
handleNodeClick(data){
console.log('handleNodeClick', data)
},
handleCheck(node, checkedData){
console.log('handleCheck: ', node, checkedData)
},
handleCheckChange(data, checked){
console.log('handleCheckChange: ', data, checked)
},
// ---------------------------------------------------------------
/**
* @description: 树节点右键事件
* @param {*} event
* @param {*} data
* @return {*}
*/
handleNodeContextMenu(event, data){
// 设置当前节点
this.$refs.tree.setCurrentNode(data);
this.currentNode = data;
// 弹出
this.menuVisible = true;
let menu = document.querySelector('#menu');
/* 菜单定位基于鼠标点击位置 */
document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法
menu.style.left = event.clientX + 20 + 'px';
menu.style.top = event.clientY - 10 + 'px';
},
/**
* @description: 取消鼠标监听事件 菜单栏
* @param {*}
* @return {*}
*/
foo() {
this.menuVisible = false;
// 要及时关掉监听,不关掉的是一个坑,不信你试试,虽然前台显示的时候没有啥毛病,加一个alert你就知道了
document.removeEventListener('click', this.foo);
},
// ---------------------------------------------------------------
/**
* @description 数组转树形数据
* @param {数据数组} list
* @param {树结构配置} config
*/
listToTree(list, config) {
let conf = {
};
Object.assign(conf, config);
const nodeMap = new Map();
const result = [];
const {
id, children, pid } = conf;
for(const node of list) {
// node[children] = node[children] || [];
nodeMap.set(node[id], node);
}
for(const node of list) {
const parent = nodeMap.get(node[pid]);
(parent ? (parent.children ? parent.children : parent.children = []) : result).push(node);
}
return result;
}
}
}
script>
<style lang="scss" scoped>
// 树节点右键 - 弹出菜单栏
.treeDemo .menu {
height: auto;
width: 120px;
color: #606266;
position: absolute;
padding: 0px;
box-sizing: border-box;
/*border: 1px solid #999999;*/
text-align: center;
background-color: #fff;
border-radius: 0.25rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.treeDemo .menu_item {
line-height: 20px;
text-align: center;
}
.treeDemo li:hover {
background-color: rgba(232, 237, 250, 0.6);
color: darkslategrey;
cursor: pointer;
}
.treeDemo li {
position: relative;
display: flex;
font-size: 14px;
align-items: center;
height: 34px;
line-height: 34px;
outline: none;
margin-top: 0px;
padding-left: 20px;
padding-right: 5px;
}
style>