cl-tree 添加 modelValue、multiple 参数
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
<template>
|
||||
<view class="cl-tree-item__wrapper" :class="[`cl-tree-item--level-${level}`, pt.className]">
|
||||
<view class="cl-tree-item-wrapper" :class="[pt.itemWrapper?.className]">
|
||||
<view
|
||||
class="cl-tree-item"
|
||||
:class="[
|
||||
{
|
||||
'is-expand': hover,
|
||||
'is-dark': isDark
|
||||
'is-dark': isDark,
|
||||
'is-checked': item.isChecked == true && ClTree?.checkable == true,
|
||||
'is-half-checked': item.isHalfChecked,
|
||||
'is-disabled': item.disabled,
|
||||
'is-multiple': ClTree?.multiple
|
||||
},
|
||||
pt.className
|
||||
pt.item?.className,
|
||||
item.isChecked == true ? pt.itemChecked?.className : ''
|
||||
]"
|
||||
:style="{
|
||||
paddingLeft: `${level * 50 + 16}rpx`
|
||||
}"
|
||||
@touchstart="onTouchStart"
|
||||
@touchend="onTouchEnd"
|
||||
@touchcancel="onTouchEnd"
|
||||
@@ -16,7 +24,7 @@
|
||||
<view class="cl-tree-item__expand" :class="[pt.expand?.className]">
|
||||
<cl-icon
|
||||
:name="icon"
|
||||
:size="pt.expandIcon?.size ?? 36"
|
||||
:size="pt.expandIcon?.size ?? 34"
|
||||
:color="pt.expandIcon?.color"
|
||||
:pt="{
|
||||
className: pt.expandIcon?.className
|
||||
@@ -32,7 +40,7 @@
|
||||
>{{ item.label }}</cl-text
|
||||
>
|
||||
|
||||
<template v-if="ClTree?.showCheckbox">
|
||||
<template v-if="showCheckbox">
|
||||
<view
|
||||
class="cl-tree-item__checkbox"
|
||||
:class="[pt.checkbox?.className]"
|
||||
@@ -99,8 +107,9 @@ const props = defineProps({
|
||||
|
||||
// 透传属性类型定义,支持自定义各部分样式和图标
|
||||
type PassThrough = {
|
||||
className?: string; // 外层自定义类名
|
||||
wrapper?: PassThroughProps; // 外层包裹属性
|
||||
item?: PassThroughProps; // 自定义类名
|
||||
itemChecked?: PassThroughProps; // 选中状态属性
|
||||
itemWrapper?: PassThroughProps; // 外层包裹属性
|
||||
expand?: PassThroughProps; // 展开区域属性
|
||||
expandIcon?: ClIconProps; // 展开图标属性
|
||||
checkbox?: PassThroughProps; // 复选框区域属性
|
||||
@@ -119,6 +128,11 @@ const ClTree = useParent<ClTreeComponentPublicInstance>("cl-tree");
|
||||
// 判断当前节点是否有子节点
|
||||
const hasChildren = computed(() => props.item.children != null && props.item.children.length > 0);
|
||||
|
||||
// 判断当前节点是否显示复选框
|
||||
const showCheckbox = computed(() => {
|
||||
return ClTree?.checkable == true && ClTree?.multiple == true;
|
||||
});
|
||||
|
||||
// 计算当前节点应显示的图标(展开/收起)
|
||||
const icon = computed(() => {
|
||||
if (ClTree == null) {
|
||||
@@ -134,6 +148,10 @@ function toExpand() {
|
||||
|
||||
// 切换当前节点的选中状态
|
||||
function toChecked() {
|
||||
if (props.item.disabled == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClTree!.setChecked(props.item.id, !(props.item.isChecked ?? false));
|
||||
}
|
||||
|
||||
@@ -144,6 +162,10 @@ const hover = ref(false);
|
||||
function onTouchStart() {
|
||||
hover.value = true;
|
||||
toExpand();
|
||||
|
||||
if (ClTree?.checkable == true && ClTree?.multiple != true && props.item.disabled != true) {
|
||||
toChecked();
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束时触发,取消hover
|
||||
@@ -153,12 +175,9 @@ function onTouchEnd() {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-tree-item__wrapper {
|
||||
@apply pl-8;
|
||||
}
|
||||
|
||||
.cl-tree-item {
|
||||
@apply flex flex-row items-center w-full p-2 rounded-lg;
|
||||
@apply flex flex-row items-center w-full rounded-lg;
|
||||
padding: 16rpx;
|
||||
|
||||
&__expand {
|
||||
@apply w-6 flex items-center justify-center;
|
||||
@@ -172,8 +191,20 @@ function onTouchEnd() {
|
||||
}
|
||||
}
|
||||
|
||||
&--level-0 {
|
||||
@apply pl-0;
|
||||
&.is-disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
@apply bg-primary-100;
|
||||
|
||||
&.is-multiple {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
&.is-dark {
|
||||
@apply bg-primary-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<view class="cl-tree">
|
||||
<view class="cl-tree" :class="[pt.className]">
|
||||
<cl-tree-item
|
||||
v-for="item in data"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:level="0"
|
||||
:pt="pt"
|
||||
:pt="props.pt"
|
||||
></cl-tree-item>
|
||||
</view>
|
||||
</template>
|
||||
@@ -13,6 +13,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, type PropType } from "vue";
|
||||
import type { ClTreeItem, ClTreeNodeInfo } from "../../types";
|
||||
import { first, isEmpty, isEqual, parsePt } from "@/cool";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-tree"
|
||||
@@ -23,6 +24,11 @@ const props = defineProps({
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 绑定值
|
||||
modelValue: {
|
||||
type: [Array, String, Number] as PropType<any | null>,
|
||||
default: null
|
||||
},
|
||||
// 树形结构数据
|
||||
list: {
|
||||
type: Array as PropType<ClTreeItem[]>,
|
||||
@@ -38,18 +44,33 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "arrow-down-s-fill"
|
||||
},
|
||||
// 是否显示复选框
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否严格的遵循父子不互相关联
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以选择节点
|
||||
checkable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
// 定义透传类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// 计算样式穿透对象
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 树数据
|
||||
const data = ref<ClTreeItem[]>(props.list);
|
||||
|
||||
@@ -266,19 +287,27 @@ function setChecked(key: string | number, flag: boolean): void {
|
||||
const nodeInfo = findNodeInfo(key); // 查找节点信息
|
||||
if (nodeInfo == null) return; // 节点不存在则返回
|
||||
|
||||
// 非多选模式下,清空所有选中状态
|
||||
if (!props.multiple) {
|
||||
clearChecked();
|
||||
}
|
||||
|
||||
// 设置当前节点选中状态
|
||||
nodeInfo.node.isChecked = flag;
|
||||
|
||||
// 非严格模式下处理父子联动
|
||||
if (!props.checkStrictly) {
|
||||
// 设置所有子孙节点的选中状态
|
||||
const descendants = getDescendants(key);
|
||||
for (let i = 0; i < descendants.length; i++) {
|
||||
descendants[i].isChecked = flag;
|
||||
}
|
||||
// 多选模式下处理
|
||||
if (props.multiple) {
|
||||
// 非严格模式下处理父子联动
|
||||
if (!props.checkStrictly) {
|
||||
// 设置所有子孙节点的选中状态
|
||||
const descendants = getDescendants(key);
|
||||
for (let i = 0; i < descendants.length; i++) {
|
||||
descendants[i].isChecked = flag;
|
||||
}
|
||||
|
||||
// 更新所有祖先节点的状态
|
||||
updateAncestorsCheckState(key);
|
||||
// 更新所有祖先节点的状态
|
||||
updateAncestorsCheckState(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,11 +415,6 @@ function setExpanded(key: string | number, flag: boolean): void {
|
||||
* @param keys 需要展开的节点id数组
|
||||
*/
|
||||
function setExpandedKeys(keys: (string | number)[]): void {
|
||||
// 先重置所有节点为收起状态
|
||||
nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
||||
info.node.isExpand = false;
|
||||
});
|
||||
|
||||
// 设置指定节点为展开状态
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const nodeInfo = findNodeInfo(keys[i]);
|
||||
@@ -445,29 +469,96 @@ function expandAll(): void {
|
||||
/**
|
||||
* 收起所有节点
|
||||
*/
|
||||
function collapseAll(): void {
|
||||
function collapseAll() {
|
||||
// 遍历所有节点,将isExpand设为false
|
||||
nodeMap.value.forEach((info: ClTreeNodeInfo) => {
|
||||
info.node.isExpand = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步绑定值
|
||||
*/
|
||||
/**
|
||||
* 同步绑定值到外部
|
||||
* 当内部选中状态变化时,更新外部的modelValue,并触发change事件
|
||||
*/
|
||||
function syncModelValue() {
|
||||
// 如果树数据为空,则不更新绑定值
|
||||
if (isEmpty(data.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前所有选中的key
|
||||
const checkedKeys = getCheckedKeys();
|
||||
|
||||
// 如果外部modelValue为null,或当前选中key与外部modelValue不一致,则更新
|
||||
if (props.modelValue == null || !isEqual(checkedKeys, props.modelValue!)) {
|
||||
// 如果多选,直接传递数组;否则只传第一个选中的key
|
||||
const value = props.multiple ? checkedKeys : first(checkedKeys);
|
||||
|
||||
emit("update:modelValue", value); // 通知外部更新modelValue
|
||||
emit("change", value); // 触发change事件
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步外部modelValue到内部选中状态
|
||||
* 当外部modelValue变化时,更新内部选中状态,并保持与外部一致
|
||||
*/
|
||||
function syncCheckedState() {
|
||||
// 如果外部modelValue为null,则不处理
|
||||
if (props.modelValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前所有选中的key
|
||||
const checkedKeys = getCheckedKeys();
|
||||
|
||||
// 如果当前选中key与外部modelValue不一致,则进行同步
|
||||
if (!isEqual(checkedKeys, props.modelValue!)) {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
setCheckedKeys(props.modelValue!); // 多选时,设置所有选中key
|
||||
} else {
|
||||
setChecked(props.modelValue!, true); // 单选时,设置单个选中key
|
||||
}
|
||||
}
|
||||
|
||||
syncModelValue(); // 同步绑定值到外部
|
||||
}
|
||||
|
||||
// 监听props.list变化,同步到内部数据
|
||||
watch(
|
||||
computed(() => props.list),
|
||||
(val: ClTreeItem[]) => {
|
||||
data.value = val;
|
||||
|
||||
// 检查选中状态
|
||||
syncCheckedState();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听modelValue变化
|
||||
watch(
|
||||
computed(() => [props.modelValue ?? 0]),
|
||||
() => {
|
||||
syncCheckedState();
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// 监听树数据变化,自动更新选中状态
|
||||
// 监听树数据变化
|
||||
watch(
|
||||
data,
|
||||
() => {
|
||||
if (!props.checkStrictly) {
|
||||
// 自动更新选中状态
|
||||
if (!props.checkStrictly && props.multiple) {
|
||||
updateAllCheckStates();
|
||||
}
|
||||
|
||||
// 更新绑定值
|
||||
syncModelValue();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
@@ -475,8 +566,9 @@ watch(
|
||||
defineExpose({
|
||||
icon: computed(() => props.icon),
|
||||
expandIcon: computed(() => props.expandIcon),
|
||||
showCheckbox: computed(() => props.showCheckbox),
|
||||
checkStrictly: computed(() => props.checkStrictly),
|
||||
checkable: computed(() => props.checkable),
|
||||
multiple: computed(() => props.multiple),
|
||||
clearChecked,
|
||||
setChecked,
|
||||
setCheckedKeys,
|
||||
|
||||
4
uni_modules/cool-ui/types/component.d.ts
vendored
4
uni_modules/cool-ui/types/component.d.ts
vendored
@@ -210,9 +210,9 @@ declare type ClSlideVerifyComponentPublicInstance = {
|
||||
declare type ClTreeComponentPublicInstance = {
|
||||
icon: string;
|
||||
expandIcon: string;
|
||||
showCheckbox: boolean;
|
||||
checkable: boolean;
|
||||
multiple: boolean;
|
||||
checkStrictly: boolean;
|
||||
accordion: boolean;
|
||||
clearChecked: () => void;
|
||||
setChecked: (key: string | number, flag: boolean) => void;
|
||||
setCheckedKeys: (keys: (string | number)[]) => void;
|
||||
|
||||
Reference in New Issue
Block a user