(component): tree组件 加入半选状态,优化data改变是视图更新,优化checkedKeys支持下级节点,默认开启过渡动画,优化关闭连线后支持行内点击

This commit is contained in:
0o张不歪o0 2022-07-11 11:08:34 +08:00
parent 401bc9db63
commit a2967fb2cb
6 changed files with 282 additions and 64 deletions

View File

@ -7,7 +7,7 @@ export default {
<script setup lang="ts"> <script setup lang="ts">
import { LayIcon } from "@layui/icons-vue"; import { LayIcon } from "@layui/icons-vue";
import LayCheckbox from "../checkbox/index.vue"; import LayCheckbox from "../checkbox/index.vue";
import { Ref, useSlots } from "vue"; import { computed, Ref, useSlots } from "vue";
import { Tree } from "./tree"; import { Tree } from "./tree";
import { Nullable } from "../../types"; import { Nullable } from "../../types";
import LayTransition from "../transition/index.vue"; import LayTransition from "../transition/index.vue";
@ -19,9 +19,9 @@ export interface TreeData {
children: TreeData[]; children: TreeData[];
parentKey: Nullable<StringOrNumber>; parentKey: Nullable<StringOrNumber>;
isRoot: boolean; isRoot: boolean;
isChecked: Ref<boolean>; isChecked: boolean;
isDisabled: Ref<boolean>; isDisabled: boolean;
isLeaf: Ref<boolean>; isLeaf: boolean;
hasNextSibling: boolean; hasNextSibling: boolean;
parentNode: Nullable<TreeData>; parentNode: Nullable<TreeData>;
} }
@ -65,7 +65,7 @@ const nodeIconType = (node: TreeData): string => {
return ""; return "";
} }
if (node.children.length !== 0) { if (node.children.length !== 0) {
return !node.isLeaf.value return !node.isLeaf
? "layui-icon-addition" ? "layui-icon-addition"
: "layui-icon-subtraction"; : "layui-icon-subtraction";
} }
@ -81,7 +81,7 @@ function handleChange(checked: boolean, node: TreeData) {
} }
function handleIconClick(node: TreeData) { function handleIconClick(node: TreeData) {
node.isLeaf.value = !node.isLeaf.value; node.isLeaf = !node.isLeaf;
} }
function handleTitleClick(node: TreeData) { function handleTitleClick(node: TreeData) {
@ -90,6 +90,39 @@ function handleTitleClick(node: TreeData) {
} }
emit("node-click", node); emit("node-click", node);
} }
function handleRowClick(node:TreeData){
if(!props.showLine){
handleTitleClick(node);
}
}
//
const isChildAllSelected=computed(()=>{
function _isChildAllSelected(node:TreeData):boolean{
if(!props.showCheckbox){
return false;
}
let childSelectNum=0;
let res=false;// true false
for (const item of node.children) {
if(item.isChecked) childSelectNum++;
}
if(childSelectNum>0) node.isChecked=true;// checkedKeys
if(childSelectNum==node.children.length){//
for (const item of node.children) {
res=_isChildAllSelected(item)
if(res) break;
}
}else{
res=true;
}
return res;
}
return function(node:TreeData):boolean{
let res=_isChildAllSelected(node)
return res;
}
})
</script> </script>
<template> <template>
@ -102,7 +135,7 @@ function handleTitleClick(node: TreeData) {
'layui-tree-setHide': node.isRoot, 'layui-tree-setHide': node.isRoot,
}" }"
> >
<div class="layui-tree-entry"> <div class="layui-tree-entry" @click="handleRowClick(node)">
<div class="layui-tree-main"> <div class="layui-tree-main">
<span <span
:class="[ :class="[
@ -110,12 +143,12 @@ function handleTitleClick(node: TreeData) {
{ 'layui-tree-iconClick': true }, { 'layui-tree-iconClick': true },
]" ]"
> >
<lay-icon :type="nodeIconType(node)" @click="handleIconClick(node)" /> <lay-icon :type="nodeIconType(node)" @click.stop="handleIconClick(node)" />
</span> </span>
<lay-checkbox <lay-checkbox
v-if="showCheckbox" v-if="showCheckbox"
:modelValue="node.isChecked.value" :modelValue="node.isChecked"
:disabled="node.isDisabled.value" :disabled="node.isDisabled"
skin="primary" skin="primary"
label="" label=""
@change=" @change="
@ -123,13 +156,14 @@ function handleTitleClick(node: TreeData) {
handleChange(checked, node); handleChange(checked, node);
} }
" "
:isIndeterminate='isChildAllSelected(node)'
/> />
<span <span
:class="{ :class="{
'layui-tree-txt': true, 'layui-tree-txt': true,
'layui-disabled': node.isDisabled.value, 'layui-disabled': node.isDisabled,
}" }"
@click="handleTitleClick(node)" @click.stop="handleTitleClick(node)"
> >
<template v-if="slots.title"> <template v-if="slots.title">
<slot name="title" :data="node"></slot> <slot name="title" :data="node"></slot>
@ -142,7 +176,7 @@ function handleTitleClick(node: TreeData) {
</div> </div>
<lay-transition :enable="collapseTransition"> <lay-transition :enable="collapseTransition">
<div <div
v-if="node.isLeaf.value" v-if="node.isLeaf"
class="layui-tree-pack layui-tree-showLine" class="layui-tree-pack layui-tree-showLine"
style="display: block" style="display: block"
> >

View File

@ -6,7 +6,7 @@ export default {
<script lang="ts" setup> <script lang="ts" setup>
import TreeNode from "./TreeNode.vue"; import TreeNode from "./TreeNode.vue";
import { computed, useSlots } from "vue"; import { computed, useSlots, watch,ref } from "vue";
import { useTree } from "./useTree"; import { useTree } from "./useTree";
import { TreeData } from "./tree"; import { TreeData } from "./tree";
import { StringFn, StringOrNumber, KeysType, EditType } from "./tree.type"; import { StringFn, StringOrNumber, KeysType, EditType } from "./tree.type";
@ -43,9 +43,10 @@ interface TreeEmits {
} }
const props = withDefaults(defineProps<TreeProps>(), { const props = withDefaults(defineProps<TreeProps>(), {
checkedKeys:()=>{ return [] },
showCheckbox: false, showCheckbox: false,
edit: false, edit: false,
collapseTransition: false, collapseTransition: true,
onlyIconControl: false, onlyIconControl: false,
disabled: false, disabled: false,
showLine: true, showLine: true,
@ -70,10 +71,29 @@ const className = computed(() => {
}; };
}); });
const { tree, nodeList } = useTree(props, emit); let tree=ref();
let nodeList=ref();
const loadNodeList=()=>{
let { tree:_tree, nodeList:_nodeList }=useTree(props, emit);
tree.value=_tree
nodeList.value=_nodeList.value
}
watch(
() => props.data,
() => {
loadNodeList();
},
{ deep: true,immediate:true}
)
watch(
() => props.checkedKeys,
() => {
loadNodeList()
},
)
function handleClick(node: TreeData) { function handleClick(node: TreeData) {
const originNode = tree.getOriginData(node.id); const originNode = tree.value.getOriginData(node.id);
emit("node-click", originNode); emit("node-click", originNode);
} }
</script> </script>

View File

@ -11,9 +11,9 @@ export interface TreeData {
children: TreeData[]; children: TreeData[];
parentKey: Nullable<StringOrNumber>; parentKey: Nullable<StringOrNumber>;
isRoot: boolean; isRoot: boolean;
isChecked: Ref<boolean>; isChecked: boolean;
isDisabled: Ref<boolean>; isDisabled: boolean;
isLeaf: Ref<boolean>; isLeaf: boolean;
hasNextSibling: boolean; hasNextSibling: boolean;
parentNode: Nullable<TreeData>; parentNode: Nullable<TreeData>;
} }
@ -106,21 +106,23 @@ class Tree {
children: nodeChildren ? nodeChildren : [], children: nodeChildren ? nodeChildren : [],
parentKey: parentKey, parentKey: parentKey,
isRoot: parentKey === "", isRoot: parentKey === "",
isDisabled: ref(false), isDisabled:false,
isChecked: ref(false), isChecked: false,
isLeaf: ref(false), isLeaf: false,
hasNextSibling: hasNextSibling, hasNextSibling: hasNextSibling,
parentNode: parentNode || null, parentNode: parentNode || null,
}); });
node.isDisabled.value = nodeDisabled; node.isDisabled = nodeDisabled;
node.isChecked.value = parentNode if(parentNode && parentNode.isChecked){
? parentNode.isChecked.value node.isChecked=true;
: checkedKeys.includes(nodeKey); }else{
node.isLeaf.value = parentNode node.isChecked=checkedKeys.includes(nodeKey);
? parentNode.isLeaf.value }
node.isLeaf = parentNode
? parentNode.isLeaf
: expandKeys.includes(nodeKey); : expandKeys.includes(nodeKey);
node.isLeaf.value = nodeIsLeaf; node.isLeaf = nodeIsLeaf;
if (!nodeMap.has(nodeKey)) { if (!nodeMap.has(nodeKey)) {
nodeMap.set(nodeKey, node); nodeMap.set(nodeKey, node);
@ -134,7 +136,7 @@ class Tree {
setChildrenChecked(checked: boolean, nodes: TreeData[]) { setChildrenChecked(checked: boolean, nodes: TreeData[]) {
const len = nodes.length; const len = nodes.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
nodes[i].isChecked.value = checked; nodes[i].isChecked = checked;
nodes[i].children && nodes[i].children &&
nodes[i].children.length > 0 && nodes[i].children.length > 0 &&
this.setChildrenChecked(checked, nodes[i].children); this.setChildrenChecked(checked, nodes[i].children);
@ -145,11 +147,11 @@ class Tree {
if (!parent) { if (!parent) {
return; return;
} }
parent.isChecked.value = checked; parent.isChecked = checked;
const pChild = parent.children; const pChild = parent.children;
const pChildChecked = pChild.some((c) => c.isChecked.value); const pChildChecked = pChild.some((c) => c.isChecked);
if (pChildChecked) { if (pChildChecked) {
parent.isChecked.value = true; parent.isChecked = true;
} }
if (parent.parentNode) { if (parent.parentNode) {
this.setParentChecked(checked, parent.parentNode); this.setParentChecked(checked, parent.parentNode);
@ -157,7 +159,7 @@ class Tree {
} }
setCheckedKeys(checked: boolean, node: TreeData) { setCheckedKeys(checked: boolean, node: TreeData) {
node.isChecked.value = checked; node.isChecked = checked;
// 处理上级 // 处理上级
if (node.parentNode) { if (node.parentNode) {
this.setParentChecked(checked, node.parentNode); this.setParentChecked(checked, node.parentNode);
@ -180,10 +182,10 @@ class Tree {
while (!next.done) { while (!next.done) {
const [, node] = next.value; const [, node] = next.value;
const id = Reflect.get(node, this.config.replaceFields.id); const id = Reflect.get(node, this.config.replaceFields.id);
if (node.isChecked.value) { if (node.isChecked) {
checkedKeys.push(id); checkedKeys.push(id);
} }
if (node.isLeaf.value) { if (node.isLeaf) {
expandKeys.push(id); expandKeys.push(id);
} }
next = iterator.next(); next = iterator.next();

View File

@ -185,7 +185,7 @@ export default {
::: title 半选状态 ::: title 半选状态
::: :::
::: demo 在实现全选效果时,你可能会用到 indeterminate 属性。 ::: demo 在实现全选效果时,你可能会用到 isIndeterminate 属性。
<template> <template>
<lay-checkbox name="like" skin="primary" label="1" :isIndeterminate="true" v-model="checked8">半选</lay-checkbox> <lay-checkbox name="like" skin="primary" label="1" :isIndeterminate="true" v-model="checked8">半选</lay-checkbox>

View File

@ -184,7 +184,7 @@ const data = ref([{
}]); }]);
function handleClick(node) { function handleClick(node) {
clickNode.value = node console.log(node)
} }
</script> </script>
@ -197,17 +197,185 @@ function handleClick(node) {
<template> <template>
<lay-tree <lay-tree
:data="data" :data="data2"
v-model:checkedKeys="checkedKeys" v-model:checkedKeys="checkedKeys"
:showCheckbox="showCheckbox" :showCheckbox="showCheckbox"
collapse-transition
> >
</lay-tree> </lay-tree>
<br/>
<lay-button @click="updateView">更新视图</lay-button>
<lay-button @click="updateCheckedKeys">更新checkedKeys</lay-button>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
const updateCheckedKeys=()=>{
const checkedKeys = ref([30,31]) checkedKeys.value=[4]
}
const data2 = ref([{
title: '一级1',
id: 1,
field: 'name1',
checked: true,
spread: true,
children: [{
title: '二级1-1 可允许跳转',
id: 3,
field: 'name11',
href: 'https://www.layui.com/',
children: [{
title: '三级1-1-3',
id: 23,
field: '',
children: [{
title: '四级1-1-3-1',
id: 24,
field: '',
children: [{
title: '五级1-1-3-1-1',
id: 30,
field: ''
},
{
title: '五级1-1-3-1-2',
id: 31,
field: ''
}]
}]
},
{
title: '三级1-1-1',
id: 7,
field: '',
children: [{
title: '四级1-1-1-1 可允许跳转',
id: 15,
field: '',
href: 'https://www.layui.com/doc/'
}]
},
{
title: '三级1-1-2',
id: 8,
field: '',
children: [{
title: '四级1-1-2-1',
id: 32,
field: ''
}]
}]
},
{
title: '二级1-2',
id: 4,
spread: true,
children: [{
title: '三级1-2-1',
id: 9,
field: '',
disabled: true
},
{
title: '三级1-2-2',
id: 10,
field: ''
}]
},
{
title: '二级1-3',
id: 20,
field: '',
children: [{
title: '三级1-3-1',
id: 21,
field: ''
},
{
title: '三级1-3-2',
id: 22,
field: ''
}]
}]
},
{
title: '一级2',
id: 2,
field: '',
spread: true,
children: [{
title: '二级2-1',
id: 5,
field: '',
spread: true,
children: [{
title: '三级2-1-1',
id: 11,
field: ''
},
{
title: '三级2-1-2',
id: 12,
field: ''
}]
},
{
title: '二级2-2',
id: 6,
field: '',
children: [{
title: '三级2-2-1',
id: 13,
field: ''
},
{
title: '三级2-2-2',
id: 14,
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 updateView=()=>{
data2.value[0].title='更新视图'
}
const checkedKeys = ref([2,3])
const showCheckbox = ref(true) const showCheckbox = ref(true)
</script> </script>
@ -234,25 +402,6 @@ const showLine=ref(false)
::: :::
::: title 过渡动画
:::
::: demo 使用 `collapse-transition` 属性开启展开过渡动画
<template>
<lay-tree
:data="data"
collapse-transition
>
</lay-tree>
</template>
<script setup>
import { ref } from 'vue';
</script>
:::
::: title 定义标题 ::: title 定义标题
::: :::

View File

@ -11,6 +11,19 @@
<template> <template>
<lay-timeline> <lay-timeline>
<lay-timeline-item title="1.2.x"> <lay-timeline-item title="1.2.x">
<ul>
<a name="1-2-9"></a>
<li>
<h3>1.2.9 <span class="layui-badge-rim">2022-07-12</span></h3>
<ul>
<li>[优化] tree 组件 支持更改data数据后刷新视图。 by @SmallWai</li>
<li>[优化] tree 组件 checkedKeys支持下级节点 by @SmallWai</li>
<li>[优化] tree 组件 关闭连线后启用行内点击 by @SmallWai</li>
<li>[优化] tree 组件 默认启用过渡动画 by @SmallWai</li>
<li>[新增] tree 组件 加入半选状态 by @SmallWai</li>
</ul>
</li>
</ul>
<ul> <ul>
<a name="1-2-8"></a> <a name="1-2-8"></a>
<li> <li>