✨(component): 处理 tree 组件 n 多问题
This commit is contained in:
@@ -6,7 +6,7 @@ export default {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LayIcon } from "@layui/icons-vue";
|
||||
import { computed, inject } from "vue";
|
||||
import { computed, inject, useSlots } from "vue";
|
||||
import "./index.less";
|
||||
|
||||
export interface LayCheckboxProps {
|
||||
@@ -38,6 +38,8 @@ const isGroup = computed(() => {
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const isChecked = computed({
|
||||
get() {
|
||||
if (isGroup.value) {
|
||||
@@ -123,7 +125,7 @@ const isDisabled = computed(() => {
|
||||
}"
|
||||
:lay-skin="skin"
|
||||
>
|
||||
<span class="layui-checkbox-label">
|
||||
<span class="layui-checkbox-label" v-if="slots.default || label">
|
||||
<slot>{{ label }}</slot>
|
||||
</span>
|
||||
<lay-icon
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface TreeNodeProps {
|
||||
nodeList: TreeData[];
|
||||
showCheckbox: boolean;
|
||||
showLine: boolean;
|
||||
checkStrictly: boolean;
|
||||
collapseTransition: boolean;
|
||||
onlyIconControl: boolean;
|
||||
}
|
||||
@@ -75,7 +76,7 @@ function recursiveNodeClick(node: TreeData) {
|
||||
}
|
||||
|
||||
function handleChange(checked: boolean, node: TreeData) {
|
||||
props.tree.setCheckedKeys(checked, node);
|
||||
props.tree.setCheckedKeys(checked, props.checkStrictly, node);
|
||||
}
|
||||
|
||||
function handleIconClick(node: TreeData) {
|
||||
@@ -86,8 +87,11 @@ function handleTitleClick(node: TreeData) {
|
||||
if (!props.onlyIconControl) {
|
||||
handleIconClick(node);
|
||||
}
|
||||
emit("node-click", node);
|
||||
if (!node.isDisabled) {
|
||||
emit("node-click", node);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowClick(node: TreeData) {
|
||||
if (!props.showLine) {
|
||||
handleTitleClick(node);
|
||||
@@ -96,6 +100,7 @@ function handleRowClick(node: TreeData) {
|
||||
|
||||
//判断是否半选
|
||||
const isChildAllSelected = computed(() => {
|
||||
|
||||
function _isChildAllSelected(node: TreeData): boolean {
|
||||
if (!props.showCheckbox) {
|
||||
return false;
|
||||
@@ -117,9 +122,14 @@ const isChildAllSelected = computed(() => {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return function (node: TreeData): boolean {
|
||||
let res = _isChildAllSelected(node);
|
||||
return res;
|
||||
if(props.checkStrictly) {
|
||||
return false;
|
||||
} else {
|
||||
let res = _isChildAllSelected(node);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -147,18 +157,14 @@ const isChildAllSelected = computed(() => {
|
||||
@click.stop="handleIconClick(node)"
|
||||
/>
|
||||
</span>
|
||||
<lay-checkbox
|
||||
v-if="showCheckbox"
|
||||
<lay-checkbox
|
||||
value="miss"
|
||||
skin="primary"
|
||||
:modelValue="node.isChecked"
|
||||
:disabled="node.isDisabled"
|
||||
skin="primary"
|
||||
value=""
|
||||
@change="
|
||||
(checked) => {
|
||||
handleChange(checked, node);
|
||||
}
|
||||
"
|
||||
:isIndeterminate="isChildAllSelected(node)"
|
||||
@change="(checked) => handleChange(checked, node)"
|
||||
v-if="showCheckbox"
|
||||
/>
|
||||
<span
|
||||
:class="{
|
||||
@@ -183,11 +189,12 @@ const isChildAllSelected = computed(() => {
|
||||
style="display: block"
|
||||
>
|
||||
<tree-node
|
||||
:tree="tree"
|
||||
:node-list="node.children"
|
||||
:show-checkbox="showCheckbox"
|
||||
:show-line="showLine"
|
||||
:collapse-transition="collapseTransition"
|
||||
:tree="tree"
|
||||
:checkStrictly="checkStrictly"
|
||||
:only-icon-control="onlyIconControl"
|
||||
@node-click="recursiveNodeClick"
|
||||
>
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface TreeProps {
|
||||
checkedKeys?: KeysType;
|
||||
data: OriginalTreeData;
|
||||
showCheckbox?: boolean;
|
||||
checkStrictly: boolean;
|
||||
edit?: EditType;
|
||||
collapseTransition?: boolean;
|
||||
onlyIconControl?: boolean;
|
||||
@@ -49,6 +50,7 @@ const props = withDefaults(defineProps<TreeProps>(), {
|
||||
showCheckbox: false,
|
||||
edit: false,
|
||||
collapseTransition: true,
|
||||
checkStrictly: false,
|
||||
onlyIconControl: false,
|
||||
disabled: false,
|
||||
showLine: true,
|
||||
@@ -77,11 +79,13 @@ let tree = ref();
|
||||
let nodeList = ref();
|
||||
const unWatch = ref(false);
|
||||
const initStatus = ref(false);
|
||||
|
||||
const loadNodeList = () => {
|
||||
let { tree: _tree, nodeList: _nodeList } = useTree(props, emit);
|
||||
tree.value = _tree;
|
||||
nodeList.value = _nodeList.value;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
@@ -89,6 +93,7 @@ watch(
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedKeys,
|
||||
() => {
|
||||
@@ -97,6 +102,7 @@ watch(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
tree,
|
||||
() => {
|
||||
@@ -130,6 +136,7 @@ function handleClick(node: TreeData) {
|
||||
:node-list="nodeList"
|
||||
:show-checkbox="showCheckbox"
|
||||
:show-line="showLine"
|
||||
:check-strictly="checkStrictly"
|
||||
:collapse-transition="collapseTransition"
|
||||
:only-icon-control="onlyIconControl"
|
||||
@node-click="handleClick"
|
||||
|
||||
@@ -25,6 +25,7 @@ interface ReplaceFields {
|
||||
}
|
||||
|
||||
interface TreeConfig {
|
||||
checkStrictly: boolean;
|
||||
showCheckbox: boolean;
|
||||
checkedKeys: StringOrNumber[];
|
||||
expandKeys: StringOrNumber[];
|
||||
@@ -156,15 +157,15 @@ class Tree {
|
||||
}
|
||||
}
|
||||
|
||||
setCheckedKeys(checked: boolean, node: TreeData) {
|
||||
setCheckedKeys(checked: boolean, checkStrictly: boolean, node: TreeData) {
|
||||
node.isChecked = checked;
|
||||
// 处理上级
|
||||
if (node.parentNode) {
|
||||
this.setParentChecked(checked, node.parentNode);
|
||||
}
|
||||
// 处理下级
|
||||
if (node.children) {
|
||||
this.setChildrenChecked(checked, node.children);
|
||||
if (!checkStrictly) {
|
||||
if (node.parentNode) {
|
||||
this.setParentChecked(checked, node.parentNode);
|
||||
}
|
||||
if (node.children) {
|
||||
this.setChildrenChecked(checked, node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface TreeProps {
|
||||
checkedKeys?: KeysType;
|
||||
expandKeys?: KeysType;
|
||||
data: OriginalTreeData;
|
||||
checkStrictly?: boolean;
|
||||
showCheckbox?: boolean;
|
||||
edit?: EditType;
|
||||
collapseTransition?: boolean;
|
||||
@@ -26,7 +27,6 @@ export interface TreeProps {
|
||||
title?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TreeEmits {
|
||||
(e: "update:checkedKeys", keys: KeysType): void;
|
||||
(e: "update:expandKeys", keys: KeysType): void;
|
||||
|
||||
@@ -23,6 +23,7 @@ export const useTree: UseTree = (props: TreeProps, emit: TreeEmits) => {
|
||||
showCheckbox: props.showCheckbox ?? false,
|
||||
checkedKeys: props.checkedKeys ?? [],
|
||||
expandKeys: props.expandKeys ?? [],
|
||||
checkStrictly: props.checkStrictly ?? false
|
||||
},
|
||||
props.data
|
||||
);
|
||||
|
||||
17
package/component/src/component/treeSelect/index.less
Normal file
17
package/component/src/component/treeSelect/index.less
Normal file
@@ -0,0 +1,17 @@
|
||||
.layui-tree-select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.layui-tree-select-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.layui-tree-select .layui-icon-triangle-d {
|
||||
transition: all 0.3s;
|
||||
-webkit-transition: all 0.3s;
|
||||
color: var(--global-neutral-color-8);
|
||||
}
|
||||
|
||||
.layui-tree-select .layui-icon-triangle-d.triangle {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@@ -5,12 +5,20 @@ export default {
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import "./index.less";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { getNode } from "../../utils/treeUtil";
|
||||
|
||||
export interface LayTreeSelect {
|
||||
data: any,
|
||||
modelValue: string;
|
||||
disabled: boolean;
|
||||
data: any;
|
||||
modelValue: any;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
multiple?: boolean;
|
||||
allowClear?: boolean;
|
||||
collapseTagsTooltip?: boolean;
|
||||
minCollapsedNum?: number;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export interface TreeSelectEmits {
|
||||
@@ -20,13 +28,21 @@ export interface TreeSelectEmits {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<LayTreeSelect>(), {
|
||||
disabled: false
|
||||
disabled: false,
|
||||
placeholder: "请选择",
|
||||
multiple: false,
|
||||
allowClear: false,
|
||||
collapseTagsTooltip: true,
|
||||
minCollapsedNum: 3,
|
||||
size: "md",
|
||||
});
|
||||
|
||||
const singleValue = ref();
|
||||
const multipleValue = ref(["1"]);
|
||||
const dropdownRef = ref();
|
||||
const openState = ref(false);
|
||||
const emits = defineEmits<TreeSelectEmits>();
|
||||
|
||||
const selectedValue = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
@@ -34,40 +50,85 @@ const selectedValue = computed({
|
||||
set(value) {
|
||||
emits("update:modelValue", value);
|
||||
emits("change", value);
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
[selectedValue],
|
||||
() => {
|
||||
if (props.multiple) {
|
||||
multipleValue.value = selectedValue.value.map((value: any) => {
|
||||
const node: any = getNode(props.data, value);
|
||||
node.label = node.title;
|
||||
return node;
|
||||
});
|
||||
} else {
|
||||
singleValue.value = props.data.find((item: any) => {
|
||||
return item.value === selectedValue.value;
|
||||
})?.label;
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const handleClick = (node: any) => {
|
||||
dropdownRef.value.hide();
|
||||
selectedValue.value = node.id;
|
||||
singleValue.value = node.title;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-tree-select">
|
||||
<lay-dropdown
|
||||
<div class="layui-tree-select" :class="{ 'layui-disabled': disabled }">
|
||||
<lay-dropdown
|
||||
ref="dropdownRef"
|
||||
:disabled="disabled"
|
||||
:update-at-scroll="true"
|
||||
@show="openState = true"
|
||||
@hide="openState = false">
|
||||
<lay-input v-model="singleValue"></lay-input>
|
||||
@hide="openState = false"
|
||||
>
|
||||
<lay-tag-input
|
||||
v-if="multiple"
|
||||
v-model="multipleValue"
|
||||
:size="size"
|
||||
:allow-clear="allowClear"
|
||||
:placeholder="placeholder"
|
||||
:collapseTagsTooltip="collapseTagsTooltip"
|
||||
:minCollapsedNum="minCollapsedNum"
|
||||
:disabledInput="true"
|
||||
>
|
||||
<template #suffix>
|
||||
<lay-icon
|
||||
type="layui-icon-triangle-d"
|
||||
:class="{ triangle: openState }"
|
||||
></lay-icon>
|
||||
</template>
|
||||
</lay-tag-input>
|
||||
<lay-input
|
||||
v-else
|
||||
v-model="singleValue"
|
||||
:size="size"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="true"
|
||||
>
|
||||
<template #suffix>
|
||||
<lay-icon
|
||||
type="layui-icon-triangle-d"
|
||||
:class="{ triangle: openState }"
|
||||
></lay-icon>
|
||||
</template>
|
||||
</lay-input>
|
||||
<template #content>
|
||||
<div class="layui-tree-select-content">
|
||||
<lay-tree :data="data" :onlyIconControl="true" @node-click="handleClick"></lay-tree>
|
||||
<lay-tree
|
||||
:data="data"
|
||||
:onlyIconControl="true"
|
||||
:show-checkbox="multiple"
|
||||
v-model:checkedKeys="selectedValue"
|
||||
@node-click="handleClick"
|
||||
></lay-tree>
|
||||
</div>
|
||||
</template>
|
||||
</lay-dropdown>
|
||||
</lay-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.layui-tree-select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.layui-tree-select-content {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -181,7 +181,7 @@ const components: Record<string, Plugin> = {
|
||||
LaySpace,
|
||||
LayTag,
|
||||
LayTagInput,
|
||||
LayTreeSelect
|
||||
LayTreeSelect,
|
||||
};
|
||||
|
||||
const install = (app: App, options?: InstallOptions): void => {
|
||||
|
||||
34
package/component/src/utils/treeUtil.ts
Normal file
34
package/component/src/utils/treeUtil.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export function getNode(root: any, id: string) {
|
||||
let resultNode = null;
|
||||
findNode(root, id);
|
||||
function findNode(root: any, id: string){
|
||||
if(!!root) {
|
||||
let type = Object.prototype.toString.call(root);
|
||||
if(type === '[object Object]') {
|
||||
if(root.id && root.id === id) {
|
||||
resultNode = root
|
||||
} else {
|
||||
let node = root.children || null
|
||||
findNode(node, id);
|
||||
}
|
||||
}else if(type === '[object Array]') {
|
||||
let needNode = root.find((i:any) => !!i === true && i.id === id);
|
||||
if(!!needNode) {
|
||||
resultNode = needNode;
|
||||
} else {
|
||||
if(root.length) {
|
||||
root.forEach((item: any)=>{
|
||||
if(item && item.children) {
|
||||
let node = item.children
|
||||
if(node && node.length){
|
||||
findNode(node, id);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultNode;
|
||||
}
|
||||
Reference in New Issue
Block a user