feat(tree): 完成树形组件复选框功能

This commit is contained in:
落小梅 2021-10-14 16:37:17 +08:00
parent c559a2d74a
commit 7caf70281c
7 changed files with 307 additions and 213 deletions

View File

@ -60,6 +60,7 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/one-component-per-file': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'vue/no-mutating-props': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},

View File

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

View File

@ -12,7 +12,8 @@ type EventType = 'icon' | 'node'
interface TreeEntityProps {
node: TreeNode
showCheckbox?: boolean
showCheckbox?: boolean,
updateCheckedByNode: (node: TreeNode) => void
}
interface EmitEvent {
@ -73,7 +74,14 @@ function innerClick(node: TreeNode, type: EventType) {
emit('node-click', node, type)
}
console.log(props.showCheckbox)
/**
* checkbox click
* @param arg
* @param node
*/
function handleCheckboxChange (arg: { checked: boolean, value: string }, node: TreeNode) {
props.updateCheckedByNode(node)
}
</script>
<template>
<template v-if="node.children && node.children.length > 0">
@ -96,13 +104,12 @@ console.log(props.showCheckbox)
v-if="showCheckbox"
name="name"
skin="primary"
label="1"
:checked="true"
v-model:checked="node._checked"
@change="(args) => { handleCheckboxChange(args, node) }"
>
{{ node.title }}
<!-- {{ node.title }} || {{node.id}}-->
</LayCheckbox>
<span
v-else
class="layui-tree-txt"
@click.prevent.stop="handleNodeClick(node, 'node')"
>
@ -119,8 +126,9 @@ console.log(props.showCheckbox)
v-for="(item, index) in node.children"
:key="index"
:node="item"
:show-checkbox="showCheckbox"
@node-click="innerClick"
:showCheckbox="showCheckbox"
:updateCheckedByNode="updateCheckedByNode"
/>
</div>
</div>
@ -145,12 +153,11 @@ console.log(props.showCheckbox)
name="name"
skin="primary"
label="1"
:checked="true"
v-model:checked="node._checked"
@change="(args) => { handleCheckboxChange(args, node) }"
>
{{ node.title }}
</LayCheckbox>
<span
v-else
class="layui-tree-txt"
@click.prevent.stop="handleNodeClick(node, 'node')"
>

View File

@ -163,10 +163,12 @@ const props = withDefaults(defineProps<TreeProps>(), {
const emit = defineEmits<TreeEmits>()
const { innerTreeData, updateInnerTreeData, treeWrapperClass } = useTreeData(
props,
emit
)
const {
innerTreeData,
updateInnerTreeData,
treeWrapperClass,
updateCheckedByNode,
} = useTreeData(props, emit)
function handleNodeClick(node: TreeNode, type: 'node' | 'icon') {
// icon
@ -191,8 +193,9 @@ export default {
v-for="(node, index) in innerTreeData"
:key="node.id || index"
:node="node"
:show-checkbox="showCheckbox"
@node-click="handleNodeClick"
:showCheckbox="showCheckbox"
:updateCheckedByNode="updateCheckedByNode"
/>
</div>
</template>

View File

@ -176,6 +176,7 @@ export interface TreeNode extends TreeData {
_parentNode?: Nullable<TreeNode>
_nextSibling?: Nullable<TreeNode>
_expand?: boolean
_checked?: boolean
}
/** hook type **/
@ -188,4 +189,5 @@ export type UseTreeData = (
spreadKeys: WritableComputedRef<(string | number)[]>
treeWrapperClass: ComputedRef<Recordable>
updateInnerTreeData: (treeData: TreeData[], node: TreeData) => void
updateCheckedByNode: (treeNode: TreeNode) => void
}

View File

@ -1,6 +1,6 @@
import { TreeData, TreeNode } from '/@src/module/tree/tree.type'
import { Nullable } from '/@src/module/type'
import { WritableComputedRef } from 'vue'
import { Ref, WritableComputedRef } from "vue";
/**
* parentId
@ -10,8 +10,7 @@ import { WritableComputedRef } from 'vue'
*/
export const generatorTreeData = (
data: TreeData[] | TreeNode[],
parentId: TreeNode['parentId'] = '',
checkedKeys: WritableComputedRef<(string | number)[]>
parentId: TreeNode['parentId'] = ''
): TreeNode[] => {
const innerTreeData: TreeNode[] = []
const len = data.length
@ -20,10 +19,10 @@ export const generatorTreeData = (
const inner = {
...item,
parentId: parentId,
spread: item.spread || false,
spread: item.spread || false
}
if (item.children && item.children.length > 0) {
inner.children = generatorTreeData(item.children, item.id, checkedKeys)
inner.children = generatorTreeData(item.children, item.id)
}
innerTreeData.push(inner as TreeNode)
}
@ -71,9 +70,10 @@ export const initialTreeData = (
data: TreeData[],
checkedKeys: WritableComputedRef<(string | number)[]>
): TreeNode[] => {
const innerTree = generatorTreeData(data, '', checkedKeys)
const innerTree = generatorTreeData(data, '')
setNextSiblings(innerTree)
setParentNode(innerTree)
patchCheckedKeys(innerTree, checkedKeys)
return innerTree
}
@ -123,3 +123,88 @@ export const getEmitNode = (
}
return item
}
/**
* checkedKes分发到node
* @param tree
* @param checkedKeys
* @param checked
*/
export const patchCheckedKeys = (tree: TreeNode[], checkedKeys: WritableComputedRef<(string | number)[]>, checked = false): void => {
if (!checkedKeys.value) {
return
}
const len = tree.length
for (let i = 0; i < len; i++){
tree[i]._checked = checked
const node = tree[i]
// 该节点是checked
if (checkedKeys.value.indexOf(node.id) > -1) {
node._checked = true
if (node.children && node.children.length > 0) {
patchCheckedKeys(node.children, checkedKeys, true)
}
} else {
if (node.children && node.children.length > 0) {
patchCheckedKeys(node.children, checkedKeys, false)
// 判断children是否为都选中如果是都选中的情况下父组件也得是选中
const allChildrenChecked = node.children.every(it => it._checked)
if (allChildrenChecked) {
node._checked = true
}
}
}
}
}
function updateChildren(data: TreeNode[], flag: boolean) {
for (let i = 0; i < data.length; i++) {
data[i]._checked = flag
if (data[i].children && data[i].children.length > 0) {
updateChildren(data[i].children, flag)
}
}
}
/**
*
* @param data
* @param clickNode
* @param parentNode
*/
export function updateInnerTreeDataChecked(data: TreeNode[], clickNode: TreeNode, parentNode?: TreeNode) {
const len = data.length
for (let i = 0; i < len; i++) {
const currentNode = data[i]
// 找到当前更新的节点
if (currentNode.id === clickNode.id) {
// 如果当前节点有子节点,更新子节点
if (currentNode.children && currentNode.children.length > 0) {
updateChildren(data[i].children, currentNode._checked!)
}
} else {
if (currentNode.children && currentNode.children.length > 0) {
updateInnerTreeDataChecked(currentNode.children, clickNode, currentNode)
}
}
// 当前节点有选中,父节点一定选中
if (currentNode.children && currentNode.children.length > 0) {
currentNode._checked = currentNode.children.some(it => it._checked)
}
}
}
export function getCheckedKeys(tree: TreeNode[]): (string | number)[] {
let keys: (string | number) [] = []
const len = tree.length
for (let i = 0; i < len; i++) {
const current = tree[i]
if (current._checked) {
keys.push(current.id)
if (current.children && current.children.length > 0) {
keys = [...keys, ...getCheckedKeys(current.children)]
}
}
}
return keys
}

View File

@ -1,9 +1,10 @@
import { TreeData, TreeNode, UseTreeData } from '/@src/module/tree/tree.type'
import { computed, ref, unref, watch } from 'vue'
import {
getCheckedKeys,
getTreeSpreadKeys,
initialTreeData,
} from '/@src/module/tree/treeHelper'
initialTreeData, updateInnerTreeDataChecked
} from "/@src/module/tree/treeHelper";
import { Recordable } from '/@src/module/type'
export const useTreeData: UseTreeData = (props, emit) => {
@ -43,6 +44,16 @@ export const useTreeData: UseTreeData = (props, emit) => {
{ immediate: true, deep: true }
)
/**
* watch innerTreeDate to emit checkedKeys
* @param treeData
* @param node
*/
watch(innerTreeData, tree => {
const emitCheckedKeys = getCheckedKeys(tree)
checkedKeys.value = emitCheckedKeys
}, { deep: true })
function updateInnerTreeData(treeData: TreeData[], node: TreeData): void {
for (let i = 0; i < treeData.length; i++) {
if (treeData[i].id === node.id) {
@ -67,11 +78,20 @@ export const useTreeData: UseTreeData = (props, emit) => {
}
})
/**
* checked状态到node中
* @param node
*/
function updateCheckedByNode (node: TreeNode) {
updateInnerTreeDataChecked(innerTreeData.value, node)
}
return {
spreadKeys,
checkedKeys,
innerTreeData,
updateInnerTreeData,
treeWrapperClass,
updateCheckedByNode
}
}