[更新]: 树形组件基础演示

This commit is contained in:
落小梅 2021-10-11 18:09:38 +08:00
parent 71e07cb0ac
commit 0617787e6a
7 changed files with 292 additions and 173 deletions

View File

@ -3,157 +3,227 @@
<template> <template>
<lay-tree <lay-tree
:data="data" :data="data"
:onlyIconControl="iconCtrl"
:showLine="showLine"
@node-click="handleClick"
> >
</lay-tree> </lay-tree>
</template>i <br/>
只能通过节点左侧图标来展开收缩:
<br/>
<lay-switch v-model="iconCtrl"></lay-switch>
<br/>
只能通过节点左侧图标来展开收缩:
<br/>
<lay-switch v-model="showLine"></lay-switch>
<br/>
当前点击的节点:
<br/>
<pre>
{{ clickNode }}
</pre>
</template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
const data = ref([{ const data = ref([
title: '一级1' {
,id: 1 title: '一级1',
,field: 'name1' id: 1,
,checked: true field: 'name1',
,spread: true checked: true,
,children: [{ spread: true,
title: '二级1-1 可允许跳转' children: [
,id: 3 {
,field: 'name11' title: '二级1-1 可允许跳转',
,href: 'https://www.layui.com/' id: 3,
,children: [{ field: 'name11',
title: '三级1-1-3' href: 'https://www.layui.com/',
,id: 23 children: [
,field: '' {
,children: [{ title: '三级1-1-3',
title: '四级1-1-3-1' id: 23,
,id: 24 field: '',
,field: '' children: [
,children: [{ {
title: '五级1-1-3-1-1' title: '四级1-1-3-1',
,id: 30 id: 24,
,field: '' field: '',
},{ children: [
title: '五级1-1-3-1-2' {
,id: 31 title: '五级1-1-3-1-1',
,field: '' id: 30,
}] field: '',
}] },
},{ {
title: '三级1-1-1' title: '五级1-1-3-1-2',
,id: 7 id: 31,
,field: '' field: '',
,children: [{ },
title: '四级1-1-1-1 可允许跳转' ],
,id: 15 },
,field: '' ],
,href: 'https://www.layui.com/doc/' },
}] {
},{ title: '三级1-1-1',
title: '三级1-1-2' id: 7,
,id: 8 field: '',
,field: '' children: [
,children: [{ {
title: '四级1-1-2-1' title: '四级1-1-1-1 可允许跳转',
,id: 32 id: 15,
,field: '' field: '',
}] href: 'https://www.layui.com/doc/',
}] },
},{ ],
title: '二级1-2' },
,id: 4 {
,spread: true title: '三级1-1-2',
,children: [{ id: 8,
title: '三级1-2-1' field: '',
,id: 9 children: [
,field: '' {
,disabled: true title: '四级1-1-2-1',
},{ id: 32,
title: '三级1-2-2' field: '',
,id: 10 },
,field: '' ],
}] },
},{ ],
title: '二级1-3' },
,id: 20 {
,field: '' title: '二级1-2',
,children: [{ id: 4,
title: '三级1-3-1' spread: true,
,id: 21 children: [
,field: '' {
},{ title: '三级1-2-1',
title: '三级1-3-2' id: 9,
,id: 22 field: '',
,field: '' disabled: true,
}] },
}] {
},{ title: '三级1-2-2',
title: '一级2' id: 10,
,id: 2 field: '',
,field: '' },
,spread: true ],
,children: [{ },
title: '二级2-1' {
,id: 5 title: '二级1-3',
,field: '' id: 20,
,spread: true field: '',
,children: [{ children: [
title: '三级2-1-1' {
,id: 11 title: '三级1-3-1',
,field: '' id: 21,
},{ field: '',
title: '三级2-1-2' },
,id: 12 {
,field: '' title: '三级1-3-2',
}] id: 22,
},{ field: '',
title: '二级2-2' },
,id: 6 ],
,field: '' },
,children: [{ ],
title: '三级2-2-1' },
,id: 13 {
,field: '' title: '一级2',
},{ id: 2,
title: '三级2-2-2' field: '',
,id: 14 spread: true,
,field: '' children: [
,disabled: true {
}] title: '二级2-1',
}] id: 5,
},{ field: '',
title: '一级3' spread: true,
,id: 16 children: [
,field: '' {
,children: [{ title: '三级2-1-1',
title: '二级3-1' id: 11,
,id: 17 field: '',
,field: '' },
,fixed: true {
,children: [{ title: '三级2-1-2',
title: '三级3-1-1' id: 12,
,id: 18 field: '',
,field: '' },
},{ ],
title: '三级3-1-2' },
,id: 19 {
,field: '' title: '二级2-2',
}] id: 6,
},{ field: '',
title: '二级3-2' children: [
,id: 27 {
,field: '' title: '三级2-2-1',
,children: [{ id: 13,
title: '三级3-2-1' field: '',
,id: 28 },
,field: '' {
},{ title: '三级2-2-2',
title: '三级3-2-2' id: 14,
,id: 29 field: '',
,field: '' disabled: true,
}] },
}] ],
}]) },
],
},
{
title: '一级3',
id: 16,
field: '',
children: [
{
title: '二级3-1',
id: 17,
field: '',
fixed: true,
children: [
{
title: '三级3-1-1',
id: 18,
field: '',
},
{
title: '三级3-1-2',
id: 19,
field: '',
},
],
},
{
title: '二级3-2',
id: 27,
field: '',
children: [
{
title: '三级3-2-1',
id: 28,
field: '',
},
{
title: '三级3-2-2',
id: 29,
field: '',
},
],
},
],
},
])
const iconCtrl = ref(false)
const showLine = ref(true)
const clickNode = ref(null)
function handleClick(node) {
clickNode.value = node
}
</script> </script>
::: :::

View File

@ -7,8 +7,9 @@ export default {
import LayIcon from '../icon' import LayIcon from '../icon'
import { TreeNode } from '/@src/module/tree/tree.type' import { TreeNode } from '/@src/module/tree/tree.type'
interface TreeEntityProps { interface TreeEntityProps{
node: TreeNode node: TreeNode
onlyIconControl: boolean
} }
interface EmitEvent { interface EmitEvent {
@ -31,7 +32,7 @@ const renderLineShort = (node: TreeNode): boolean => {
(node._parentNode._nextSibling === null || (node._parentNode._nextSibling === null ||
//线 //线
(node._parentNode._nextSibling && !node._parentNode._nextSibling.children)) (node._parentNode._nextSibling && !node._parentNode._nextSibling.children))
) ) as boolean
} }
/** /**
* 展开收起 icon样式 * 展开收起 icon样式
@ -41,10 +42,22 @@ const nodeIconType = (node: TreeNode): string => {
return !node.spread ? 'layui-icon-addition' : 'layui-icon-subtraction' return !node.spread ? 'layui-icon-addition' : 'layui-icon-subtraction'
} }
/**
* 发射至外层
* @param node
*/
function handleNodeClick (node: TreeNode) { function handleNodeClick (node: TreeNode) {
emit('node-click', node) emit('node-click', node)
} }
/**
* 递归事件
* @param node
*/
function innerClick (node: TreeNode) {
emit('node-click', node)
}
</script> </script>
<template> <template>
<template v-if="node.children && node.children.length > 0"> <template v-if="node.children && node.children.length > 0">
@ -56,9 +69,9 @@ function handleNodeClick (node: TreeNode) {
}" }"
> >
<div class="layui-tree-entry"> <div class="layui-tree-entry">
<div class="layui-tree-main" @click="handleNodeClick(node)"> <div class="layui-tree-main" @click.prevent.stop="!onlyIconControl && handleNodeClick(node)">
<span class="layui-tree-iconClick layui-tree-icon"> <span class="layui-tree-iconClick layui-tree-icon">
<LayIcon :type="nodeIconType(node)"></LayIcon> <LayIcon :type="nodeIconType(node)" @click.prevent.stop="handleNodeClick(node)"></LayIcon>
</span> </span>
<span class="layui-tree-txt">{{ node.title }}</span> <span class="layui-tree-txt">{{ node.title }}</span>
</div> </div>
@ -72,7 +85,8 @@ function handleNodeClick (node: TreeNode) {
v-for="(item, index) in node.children" v-for="(item, index) in node.children"
:key="index" :key="index"
:node="item" :node="item"
@node-click="handleNodeClick" @node-click="innerClick"
:onlyIconControl="onlyIconControl"
></LayTreeEntity> ></LayTreeEntity>
</div> </div>
</div> </div>
@ -85,9 +99,9 @@ function handleNodeClick (node: TreeNode) {
}" }"
> >
<div class="layui-tree-entry"> <div class="layui-tree-entry">
<div class="layui-tree-main"> <div class="layui-tree-main" @click.prevent.stop="!onlyIconControl && handleNodeClick(node)">
<span class="layui-tree-iconClick"> <span class="layui-tree-iconClick">
<LayIcon type="layui-icon-file"></LayIcon> <LayIcon type="layui-icon-file" @click.prevent.stop="handleNodeClick(node)"></LayIcon>
</span> </span>
<span class="layui-tree-txt">{{ node.title }}</span> <span class="layui-tree-txt">{{ node.title }}</span>
</div> </div>

View File

@ -3,7 +3,7 @@ import Component from './index.vue'
import type { IDefineComponent } from '../type/index' import type { IDefineComponent } from '../type/index'
Component.install = (app: App) => { Component.install = (app: App) => {
app.component(Component.name || 'LayTree', Component) app.component(Component.name || 'LayTree', Component)
} }
export default Component as IDefineComponent export default Component as IDefineComponent

View File

@ -3,12 +3,13 @@ import { VNode, VNodeChild } from 'vue'
import TreeEntity from './TreeEntity.vue' import TreeEntity from './TreeEntity.vue'
import { useTreeData } from '/@src/module/tree/useTreeData' import { useTreeData } from '/@src/module/tree/useTreeData'
import { TreeNode } from '/@src/module/tree/tree.type' import { TreeNode } from '/@src/module/tree/tree.type'
import { getEmitNode } from '/@src/module/tree/treeHelper'
type EditAction = 'add' | 'update' | 'del' type EditAction = 'add' | 'update' | 'del'
type EditType = boolean | EditAction[] type EditType = boolean | EditAction[]
export declare interface TreeData { interface TreeData {
/** /**
* 节点唯一索引值用于对指定节点进行各类操作 * 节点唯一索引值用于对指定节点进行各类操作
*/ */
@ -107,40 +108,42 @@ export declare interface TreeProps {
} }
export interface EmitData { interface EmitData {
/** /**
* 当前点击的节点数据 * 当前点击的节点数据
*/ */
data: TreeData data: TreeData
/** /**
* 节点的展开状态 * 节点的展开状态
* remove
*/ */
state: 'open' | 'close' | 'normal' state?: 'open' | 'close' | 'normal'
/** /**
* 当前节点元素 * 当前节点元素
* remove
*/ */
elem: Element | VNode | VNodeChild elem?: Element | VNode | VNodeChild
} }
export interface TreeEmits { interface TreeEmits {
/** /**
* 节点被点击后触发 * 节点被点击后触发
* @param e 事件 * @param e 事件
* @param treeNode * @param treeNode
*/ */
(e: 'on-click', treeNode: EmitData): void (e: 'node-click', treeNode: EmitData): void
/** /**
* 点击复选框时触发 * 点击复选框时触发
* @param e 事件 * @param e 事件
* @param treeNode * @param treeNode
*/ */
(e: 'on-check', treeNode: EmitData): void (e: 'node-check', treeNode: EmitData): void
/** // /**
* 操作节点的回 // *
* @param e // * @param e
* @param treeNode // * @param treeNode
*/ // */
(e: 'on-operate', treeNode: EmitData): void // (e: 'node-operate', treeNode: EmitData): void
(e: 'update:spreadKeys', spreadKeys: string[]): void (e: 'update:spreadKeys', spreadKeys: string[]): void
} }
@ -163,6 +166,8 @@ const {
function handleNodeClick(node: TreeNode) { function handleNodeClick(node: TreeNode) {
updateInnerTreeData(innerTreeData.value, node) updateInnerTreeData(innerTreeData.value, node)
const emitNode = getEmitNode(props.data!, node)
emit('node-click', { data: emitNode! })
} }
</script> </script>
@ -177,6 +182,7 @@ export default {
v-for="(node) in innerTreeData" v-for="(node) in innerTreeData"
:key="node.id" :key="node.id"
:node="node" :node="node"
:onlyIconControl="onlyIconControl"
@node-click="handleNodeClick" @node-click="handleNodeClick"
></TreeEntity> ></TreeEntity>
</div> </div>

View File

@ -116,32 +116,37 @@ export interface EmitData {
/** /**
* *
*/ */
state: 'open' | 'close' | 'normal' state?: 'open' | 'close' | 'normal'
/** /**
* *
*/ */
elem: Element | VNode | VNodeChild elem?: Element | VNode | VNodeChild
} }
export interface TreeEmits { export declare interface TreeEmits {
/** /**
* *
* @param e * @param e
* @param treeNode * @param treeNode
*/ */
(e: 'on-click', treeNode: EmitData): void (e: 'node-click', treeNode: EmitData): void
/** /**
* *
* @param e * @param e
* @param treeNode * @param treeNode
*/ */
(e: 'on-check', treeNode: EmitData): void (e: 'node-check', treeNode: EmitData): void
/** /**
* *
* @param e * @param e
* @param treeNode * @param treeNode
*/ */
(e: 'on-operate', treeNode: EmitData): void // (e: 'on-operate', treeNode: EmitData): void
/**
* update:spreadKeys
* @param e
* @param spreadKeys
*/
(e: 'update:spreadKeys', spreadKeys: (string | number)[]): void (e: 'update:spreadKeys', spreadKeys: (string | number)[]): void
} }

View File

@ -1,4 +1,5 @@
import { TreeData, TreeNode } from '/@src/module/tree/tree.type' import { TreeData, TreeNode } from '/@src/module/tree/tree.type'
import { Nullable } from '/@src/module/type'
/** /**
* parentId * parentId
@ -83,3 +84,26 @@ export const getTreeSpreadKeys = (data: TreeData[]): (string | number)[] => {
} }
return keys return keys
} }
/**
*
* @param data
* @param node
*/
export const getEmitNode = (data: TreeData[], node: TreeNode): Nullable<TreeData> => {
let item: Nullable<TreeData> = null
const len = data.length
for (let i = 0; i < len; i++) {
if (data[i].id === node.id) {
item = data[i]
break
}
if (data[i].children && data[i].children.length > 0) {
item = getEmitNode(data[i].children, node)
if (item) {
break
}
}
}
return item
}

View File

@ -12,7 +12,7 @@ export const useTreeData: UseTreeData = (props, emit) => {
} }
return getTreeSpreadKeys(props.data) return getTreeSpreadKeys(props.data)
}, },
set: (value) => { set: (value: (string | number)[]) => {
emit('update:spreadKeys', value) emit('update:spreadKeys', value)
} }
}) })