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

@@ -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
}
}