初始提交

This commit is contained in:
2026-01-04 11:09:06 +08:00
commit 8fa31df250
1326 changed files with 213907 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
<template>
<text :class="classObj.wrapper" @click.stop="handleClick">
<text
:class="[classObj.input, {'is-indeterminate': indeterminate, 'is-checked': checked, 'is-disabled': disabled}]">
<text :class="classObj.inner"></text>
</text>
</text>
</template>
<script>
export default {
data() {
return {
classObj: {}
}
},
props: {
type: {
type: String,
validator(t) {
return t === 'radio' || t === 'checkbox'
}
},
checked: Boolean,
disabled: Boolean,
indeterminate: Boolean
},
created() {
this.classObj = {
wrapper: `ly-${this.type}`,
input: `ly-${this.type}__input`,
inner: `ly-${this.type}__inner`
}
},
methods: {
handleClick() {
this.$emit('check', this.checked);
}
}
}
</script>
<style>
/* lyRadio/lyCheckbox-start */
.ly-checkbox,
.ly-radio {
color: #606266;
font-weight: 500;
font-size: 28rpx;
cursor: pointer;
user-select: none;
padding-right: 16rpx
}
.ly-checkbox__input,
.ly-radio__input {
cursor: pointer;
outline: 0;
line-height: 1;
vertical-align: middle
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner,
.ly-radio__input.is-disabled .ly-radio__inner {
background-color: #edf2fc;
border-color: #DCDFE6;
cursor: not-allowed
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner::after,
.ly-radio__input.is-disabled .ly-radio__inner::after {
cursor: not-allowed;
border-color: #C0C4CC
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner+.ly-checkbox__label,
.ly-radio__input.is-disabled .ly-radio__inner+.ly-radio__label {
cursor: not-allowed
}
.ly-checkbox__input.is-disabled.is-checked .ly-checkbox__inner,
.ly-radio__input.is-disabled.is-checked .ly-radio__inner {
background-color: #F2F6FC;
border-color: #DCDFE6
}
.ly-checkbox__input.is-disabled.is-checked .ly-checkbox__inner::after,
.ly-radio__input.is-disabled.is-checked .ly-radio__inner::after {
border-color: #C0C4CC
}
.ly-checkbox__input.is-disabled.is-indeterminate .ly-checkbox__inner {
background-color: #F2F6FC;
border-color: #DCDFE6
}
.ly-checkbox__input.is-disabled.is-indeterminate .ly-checkbox__inner::before {
background-color: #C0C4CC;
border-color: #C0C4CC
}
.ly-checkbox__input.is-checked .ly-checkbox__inner,
.ly-radio__input.is-checked .ly-radio__inner,
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner {
background-color: #2979ff;
border-color: #2979ff
}
.ly-checkbox__input.is-disabled+text.ly-checkbox__label,
.ly-radio__input.is-disabled+text.ly-radio__label {
color: #C0C4CC;
cursor: not-allowed
}
.ly-checkbox__input.is-checked .ly-checkbox__inner::after,
.ly-radio__input.is-checked .ly-radio__inner::after {
-webkit-transform: rotate(45deg) scaleY(1);
transform: rotate(45deg) scaleY(1)
}
.ly-checkbox__input.is-checked+.ly-checkbox__label,
.ly-radio__input.is-checked+.ly-radio__label {
color: #2979ff
}
.ly-checkbox__input.is-focus .ly-checkbox__inner,
.ly-radio__input.is-focus .ly-radio__inner {
border-color: #2979ff
}
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner::before {
content: '';
position: absolute;
display: block;
background-color: #FFF;
height: 6rpx;
-webkit-transform: scale(.5);
transform: scale(.5);
left: 0;
right: 0;
top: 10rpx
}
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner::after {
display: none
}
.ly-checkbox__inner,
.ly-radio__inner {
display: inline-block;
position: relative;
border: 2rpx solid #DCDFE6;
border-radius: 4rpx;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 28rpx;
height: 28rpx;
background-color: #FFF;
z-index: 1;
-webkit-transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46);
transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46)
}
.ly-radio__inner {
border-radius: 50%;
width: 34rpx !important;
height: 34rpx !important;
}
.ly-checkbox__inner::after,
.ly-radio__inner::after {
-webkit-box-sizing: content-box;
box-sizing: content-box;
content: "";
border: 2rpx solid #FFF;
border-left: 0;
border-top: 0;
height: 14rpx;
left: 10rpx;
position: absolute;
top: 2rpx;
-webkit-transform: rotate(45deg) scaleY(0);
transform: rotate(45deg) scaleY(0);
width: 6rpx;
-webkit-transition: -webkit-transform .15s ease-in .05s;
transition: -webkit-transform .15s ease-in .05s;
transition: transform .15s ease-in .05s;
transition: transform .15s ease-in .05s, -webkit-transform .15s ease-in .05s;
-webkit-transform-origin: center;
transform-origin: center
}
.ly-radio__inner::after {
left: 12rpx !important;
top: 6rpx !important;
}
/* lyRadio/lyCheckbox-end */
</style>

View File

@@ -0,0 +1,402 @@
<template>
<view ref="node" name="LyTreeNode" v-show="node.visible" class="ly-tree-node" :class="{
'is-expanded': expanded,
'is-hidden': !node.visible,
'is-checked': !node.disabled && node.checked
}" role="treeitem" @tap.stop="handleClick">
<view class="ly-tree-node__content" :class="{
'is-current': node.isCurrent && highlightCurrent
}" :style="{
'padding-left': (node.level - 1) * indent + 'px'
}">
<text @tap.stop="handleExpandIconClick" :class="[
{
'is-leaf': node.isLeaf,
expanded: !node.isLeaf && node.expanded
},
'ly-tree-node__expand-icon',
iconClass ? iconClass : 'ly-iconfont ly-icon-caret-right'
]">
</text>
<ly-checkbox v-if="checkboxVisible || radioVisible" :type="checkboxVisible ? 'checkbox' : 'radio'"
:checked="node.checked" :indeterminate="node.indeterminate" :disabled="!!node.disabled"
@check="handleCheckChange(!node.checked)" />
<text v-if="node.loading" class="ly-tree-node__loading-icon ly-iconfont ly-icon-loading">
</text>
<template v-if="node.icon && node.icon.length > 0">
<image v-if="node.icon.indexOf('/') !== -1" class="ly-tree-node__icon" mode="widthFix" :src="node.icon"
@error="handleImageError">
</image>
<text v-else class="ly-tree-node__icon" :class="node.icon"></text>
</template>
<text class="ly-tree-node__label u-line-1">{{node.label}}</text>
</view>
<view v-if="!renderAfterExpand || childNodeRendered" v-show="expanded" class="ly-tree-node__children"
role="group">
<ly-tree-node v-for="cNodeId in node.childNodesId" :nodeId="cNodeId"
:render-after-expand="renderAfterExpand" :show-checkbox="showCheckbox" :show-radio="showRadio"
:check-only-leaf="checkOnlyLeaf" :key="getNodeKey(cNodeId)" :indent="indent" :icon-class="iconClass">
</ly-tree-node>
</view>
</view>
</template>
<script>
import {
getNodeKey
} from './tool/util.js';
import lyCheckbox from './components/ly-checkbox.vue';
export default {
name: 'LyTreeNode',
componentName: 'LyTreeNode',
components: {
lyCheckbox
},
props: {
nodeId: [Number, String],
renderAfterExpand: {
type: Boolean,
default: true
},
checkOnlyLeaf: {
type: Boolean,
default: false
},
showCheckbox: {
type: Boolean,
default: false
},
showRadio: {
type: Boolean,
default: false
},
indent: Number,
iconClass: String
},
data() {
return {
node: {
indeterminate: false,
checked: false,
expanded: false
},
expanded: false,
childNodeRendered: false,
oldChecked: null,
oldIndeterminate: null,
highlightCurrent: false
};
},
inject: ['tree'],
computed: {
checkboxVisible() {
if (this.checkOnlyLeaf) {
return this.showCheckbox && this.node.isLeaf;
}
return this.showCheckbox;
},
radioVisible() {
if (this.checkOnlyLeaf) {
return this.showRadio && this.node.isLeaf;
}
return this.showRadio;
}
},
watch: {
'node.indeterminate'(val) {
this.handleSelectChange(this.node.checked, val);
},
'node.checked'(val) {
this.handleSelectChange(val, this.node.indeterminate);
},
'node.expanded'(val) {
this.$nextTick(() => this.expanded = val);
if (val) {
this.childNodeRendered = true;
}
}
},
methods: {
getNodeKey(nodeId) {
let node = this.tree.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.tree.nodeKey, node.data);
},
handleSelectChange(checked, indeterminate) {
if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) {
if (this.checkOnlyLeaf && !this.node.isLeaf) return;
if (this.checkboxVisible) {
const allNodes = this.tree.store._getAllNodes();
this.tree.$emit('check-change', {
checked,
indeterminate,
node: this.node,
data: this.node.data,
checkedall: allNodes.every(item => item.checked)
});
} else {
this.tree.$emit('radio-change', {
checked,
node: this.node,
data: this.node.data,
checkedall: false
});
}
}
if (!this.expanded && this.tree.expandOnCheckNode && checked) {
this.handleExpandIconClick();
}
this.oldChecked = checked;
this.indeterminate = indeterminate;
},
handleClick() {
this.tree.store.setCurrentNode(this.node);
this.tree.$emit('current-change', {
node: this.node,
data: this.tree.store.currentNode ? this.tree.store.currentNode.data : null,
currentNode: this.tree.store.currentNode
});
this.tree.currentNode = this.node;
if (this.tree.expandOnClickNode) {
this.handleExpandIconClick();
}
if (this.tree.checkOnClickNode && !this.node.disabled) {
(this.checkboxVisible || this.radioVisible) && this.handleCheckChange(!this.node.checked, true);
}
this.tree.$emit('node-click', this.node);
},
handleExpandIconClick() {
if (this.node.isLeaf) return;
if (this.expanded) {
this.tree.$emit('node-collapse', this.node);
this.node.collapse();
} else {
this.node.expand();
this.tree.$emit('node-expand', this.node);
if (this.tree.accordion) {
uni.$emit(`${this.tree.elId}-tree-node-expand`, this.node);
}
}
},
handleCheckChange(checked, isFromClick = false) {
if (this.node.disabled) return;
if (this.tree.checkOnClickNode && !isFromClick) {
return this.handleClick()
}
if (this.checkboxVisible) {
this.node.setChecked(checked, !(this.tree.checkStrictly || this.checkOnlyLeaf));
} else {
this.node.setRadioChecked(checked);
}
this.$nextTick(() => {
this.tree.$emit('check', {
node: this.node,
data: this.node.data,
checkedNodes: this.tree.store.getCheckedNodes(),
checkedKeys: this.tree.store.getCheckedKeys(),
halfCheckedNodes: this.tree.store.getHalfCheckedNodes(),
halfCheckedKeys: this.tree.store.getHalfCheckedKeys()
});
});
uni.$emit('updateKey')
},
handleImageError() {
this.node.icon = this.tree.defaultNodeIcon;
}
},
created() {
if (!this.tree) {
throw new Error('Can not find node\'s tree.');
}
this.node = this.tree.store.nodesMap[this.nodeId];
this.highlightCurrent = this.tree.highlightCurrent;
if (this.node.expanded) {
this.expanded = true;
this.childNodeRendered = true;
}
const props = this.tree.props || {};
const childrenKey = props['children'] || 'children';
this.$watch(`node.data.${childrenKey}`, () => {
this.node.updateChildren();
});
if (this.tree.accordion) {
uni.$on(`${this.tree.elId}-tree-node-expand`, node => {
if (this.node.id !== node.id && this.node.level === node.level) {
this.node.collapse();
}
});
}
},
beforeDestroy() {
this.$parent = null;
}
};
</script>
<style>
.ly-tree-node {
white-space: nowrap;
outline: 0
}
.ly-tree-node__content {
display: flex;
align-items: center;
height: 70rpx;
}
.ly-tree-node__content.is-current {
background-color: #F5F7FA;
}
.ly-tree-node__content>.ly-tree-node__expand-icon {
padding: 12rpx;
}
.ly-tree-node__checkbox {
display: flex;
margin-right: 16rpx;
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__checkbox>image {
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__expand-icon {
color: #C0C4CC;
font-size: 28rpx;
-webkit-transform: rotate(0);
transform: rotate(0);
-webkit-transition: -webkit-transform .3s ease-in-out;
transition: -webkit-transform .3s ease-in-out;
transition: transform .3s ease-in-out;
transition: transform .3s ease-in-out, -webkit-transform .3s ease-in-out
}
.ly-tree-node__expand-icon.expanded {
-webkit-transform: rotate(90deg);
transform: rotate(90deg)
}
.ly-tree-node__expand-icon.is-leaf {
color: transparent;
}
.ly-tree-node__icon {
width: 34rpx;
/* height: 34rpx; */
overflow: hidden;
margin-right: 16rpx;
}
.ly-tree-node__label {
font-size: 28rpx
}
.ly-tree-node__loading-icon {
margin-right: 16rpx;
font-size: 28rpx;
color: #C0C4CC;
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite
}
.ly-tree-node>.ly-tree-node__children {
overflow: hidden;
background-color: transparent
}
.ly-tree-node>.ly-tree-node__children.collapse-transition {
transition: height .3s ease-in-out;
}
.ly-tree-node.is-expanded>.ly-tree-node__children {
display: block
}
.ly-tree-node_collapse {
overflow: hidden;
padding-top: 0;
padding-bottom: 0;
}
/* lyTree-end */
/* iconfont-start */
@font-face {
font-family: "ly-iconfont";
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAPsAAsAAAAACKwAAAOeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqFDIQPATYCJAMMCwgABCAFhG0HQBtfB8gekiSCdAwUAKgCFMA5Hj7H0PeTlABUr57PVyGqugqzSWJnNwWoWJjx/9rUr4TPL1ZSQpU2mycqwoRwIN3p+MkqMqyEW+OtMBLPSUBb8v//XtWMKTavxYIUsT/Wy1qbQzkBDOYEKGB7dVpPyVqgCnJNwvMvhZl10nMCtQbFoPVhY8ZDncJfF4grbqpQ13AqE52hWqgcOFrEQ6hWnW5VfMCD7Pfjn4WoI6nI/K0bl0MNGPBz0qcflVqYnvCA4vNDPUXGPFCIw8HgtsqiOK9SrW2smm6sVITElWlpISMdVBn8wyMJopLfXg+myZ48KCrSkvj9g37U1ItbXYke4APwXxK3N4TuehyBfmM0I3zbNdt7uk3VnjPtzX0rnIl7z7bZvb/thHohsu9QuykKo+Cws4nL7LsPmI3n2qN9B9upZEIKd4hu0NCKi0rt7fNtdl+I1N25hOJMDQK6odS123tROR7Pg8toEhDaF+kR0TYjxW6M58F5+ZNQOxmZHtE2g+IYjxjlNy/yIRQpCmrgq5R4/3jx8PvT8Ha8d3/xiLnt4EGyaDnznzRv8vpyZ+9TFHf/ntX9e59A+b6+fPHd5+dy0wYHVvHOroWbnWe879O9DnL53bN/gUHuwm28b/n8i/V3ry4E3IoXNqS6Rvs0LhJxeNVjoUkM3LKosU+0a6rh45FVvLt+2oz7Zd53b4QOy7/9snDXHbqVu+A+f8r7PnM2H8kXrWm5c8/vLu7LqRee7HW637mz3kHc5U/RCXf25d7G8tkdgEfwIpzpkknGpaMw3ww55q9Mn9OQNyua/wB/49OOWydn4eL/6roCfjx6FMmcxfJStYRKfd3UwoHiML4rF4uMSK+SvYTuNxMHrpl8yd3Q6v32cAeo/KFaowBJlQHIqo3zi3geKtRZhErVlqDWnOGn67QRKkWpwaw1AkKza5A0egFZszf8In4HFTp9h0rNUQm1NqP1lXUmgyuDBVUlNYi2gHA98FnokUreOZaac1xV1JlMMZGKEs+QdCLVrgynPhUcO0pzzYyUjDAReGSYeBl13YCEIrCpLhOWlGE+mWRD35TQAw8UawRKJVEGQrMAwekCPpaMlpTOz49FmeZwqcREX1t3Ikoo4dMTaQmpBfzhRn9R30uZXTKXKUOSmLSKEQIeYhjqKZcrcIzhMLLRrJMSrA35UF4yGMaWGhPHm733dwJq+Z/NkSJHUXemCirjgpuWrHMD1eC+mQUAAAA=') format('woff2');
}
.ly-iconfont {
font-family: "ly-iconfont" !important;
font-size: 30rpx;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ly-icon-caret-right:before {
content: "\e8ee";
}
.ly-icon-loading:before {
content: "\e657";
}
/* iconfont-end */
/* animate-start */
@keyframes rotating {
0% {
-webkit-transform: rotateZ(0);
transform: rotateZ(0)
}
100% {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg)
}
}
/* animate-end */
</style>

View File

@@ -0,0 +1,639 @@
<template>
<view>
<template v-if="showLoading">
<view class="ly-loader ly-flex-center">
<view class="ly-loader-inner">{{$t('component.drawer.loadingText')}}</view>
</view>
</template>
<template v-else>
<JnpfEmpty v-if="isEmpty || !visible"></JnpfEmpty>
<view :key="updateKey" class="ly-tree" :class="{'is-empty': isEmpty || !visible}" role="tree"
name="LyTreeExpand" v-if="show">
<ly-tree-node v-for="nodeId in childNodesId" :nodeId="nodeId" :render-after-expand="renderAfterExpand"
:show-checkbox="showCheckbox" :show-radio="showRadio" :check-only-leaf="checkOnlyLeaf"
:key="getNodeKey(nodeId)" :indent="indent" :icon-class="iconClass">
</ly-tree-node>
</view>
</template>
</view>
</template>
<script>
import TreeStore from './model/tree-store.js';
import {
getNodeKey
} from './tool/util.js';
import LyTreeNode from './ly-tree-node.vue';
export default {
name: 'LyTree',
componentName: 'LyTree',
components: {
LyTreeNode
},
data() {
return {
updateKey: new Date().getTime(), // 数据更新的时候,重新渲染树
elId: `ly_${Math.ceil(Math.random() * 10e5).toString(36)}`,
visible: true,
store: {
ready: false
},
currentNode: null,
childNodesId: [],
mathKey: 1,
show: true
};
},
provide() {
return {
tree: this
}
},
props: {
// 展示数据
treeData: Array,
// 自主控制loading加载避免数据还没获取到的空档出现“暂无数据”字样
ready: {
type: Boolean,
default: true
},
// 内容为空的时候展示的文本
emptyText: {
type: String,
default: '暂无数据'
},
// 是否在第一次展开某个树节点后才渲染其子节点
renderAfterExpand: {
type: Boolean,
default: true
},
// 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
nodeKey: String,
// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
checkStrictly: Boolean,
// 是否默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: true
},
// 切换全部展开、全部折叠
toggleExpendAll: Boolean,
// 是否在点击节点的时候展开或者收缩节点, 默认值为 true如果为 false则只有点箭头图标的时候才会展开或者收缩节点
expandOnClickNode: {
type: Boolean,
default: true
},
// 选中的时候展开节点
expandOnCheckNode: {
type: Boolean,
default: true
},
// 是否在点击节点的时候选中节点,默认值为 false即只有在点击复选框时才会选中节点
checkOnClickNode: Boolean,
checkDescendants: {
type: Boolean,
default: false
},
// 展开子节点的时候是否自动展开父节点
autoExpandParent: {
type: Boolean,
default: true
},
// 默认勾选的节点的 key 的数组
defaultCheckedKeys: Array,
// 默认展开的节点的 key 的数组
defaultExpandedKeys: Array,
// 是否展开当前节点的父节点
expandCurrentNodeParent: Boolean,
// 当前选中的节点
currentNodeKey: [String, Number],
// 是否最后一层叶子节点才显示单选/多选框
checkOnlyLeaf: {
type: Boolean,
default: false
},
// 节点是否可被选择
showCheckbox: {
type: Boolean,
default: false
},
// 节点单选
showRadio: {
type: Boolean,
default: false
},
// 配置选项
props: {
type: [Object, Function],
default () {
return {
children: 'children', // 指定子树为节点对象的某个属性值
label: 'label', // 指定节点标签为节点对象的某个属性值
disabled: 'disabled' // 指定节点选择框是否禁用为节点对象的某个属性值
};
}
},
// 是否懒加载子节点,需与 load 方法结合使用
lazy: {
type: Boolean,
default: false
},
// 是否高亮当前选中节点,默认值是 false
highlightCurrent: Boolean,
// 加载子树数据的方法,仅当 lazy 属性为true 时生效
load: Function,
// 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
filterNodeMethod: Function,
// 搜索时是否展示匹配项的所有子节点
childVisibleForFilterNode: {
type: Boolean,
default: false
},
// 是否每次只打开一个同级树节点展开
accordion: Boolean,
// 相邻级节点间的水平缩进,单位为像素
indent: {
type: Number,
default: 18
},
// 自定义树节点的展开图标
iconClass: String,
// 是否显示节点图标如果配置为true,需要配置props中对应的图标属性名称
showNodeIcon: {
type: Boolean,
default: false
},
// 当节点图标显示出错时,显示的默认图标
defaultNodeIcon: {
type: String,
default: ''
},
// 如果数据量较大建议不要在node节点中添加parent属性会造成性能损耗
isInjectParentInNode: {
type: Boolean,
default: false
}
},
computed: {
isEmpty() {
this.$nextTick(() => {
if (this.store.root) {
const childNodes = this.store.root.getChildNodes(this.childNodesId);
return !childNodes || childNodes.length === 0 || childNodes.every(({
visible
}) => !visible);
}
return true;
})
},
showLoading() {
//不要删除
const a = this.mathKey
return !(this.store.getReady() && this.ready);
}
},
watch: {
toggleExpendAll(newVal) {
this.store.toggleExpendAll(newVal);
},
defaultCheckedKeys(newVal) {
this.store.setDefaultCheckedKey(newVal);
},
defaultExpandedKeys(newVal) {
this.store.defaultExpandedKeys = newVal;
this.store.setDefaultExpandedKeys(newVal);
},
checkStrictly(newVal) {
this.store.checkStrictly = newVal || this.checkOnlyLeaf;
},
'store.root.childNodesId'(newVal) {
this.childNodesId = newVal;
},
'store.root.visible'(newVal) {
this.visible = newVal;
},
childNodesId() {
this.$nextTick(() => {
this.$emit('ly-tree-render-completed');
});
},
treeData: {
handler(newVal) {
this.updateKey = new Date().getTime();
this.store.setData(newVal);
},
deep: true
}
},
methods: {
/*
* @description 对树节点进行筛选操作
* @method filter
* @param {all} value 在 filter-node-method 中作为第一个参数
* @param {Object} data 搜索指定节点的节点数据不传代表搜索所有节点假如要搜索A节点下面的数据那么nodeData代表treeData中A节点的数据
*/
filter(value, data) {
if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
this.store.filter(value, data);
this.handleUpdateKey()
},
handleUpdateKey() {
// #ifndef MP
this.updateKey = new Date().getTime();
// #endif
},
/*
* @description 获取节点的唯一标识符
* @method getNodeKey
* @param {String, Number} nodeId
* @return {String, Number} 匹配到的数据中的某一项数据
*/
getNodeKey(nodeId) {
let node = this.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.nodeKey, node.data);
},
/*
* @description 获取节点路径
* @method getNodePath
* @param {Object} data 节点数据
* @return {Array} 路径数组
*/
getNodePath(data) {
return this.store.getNodePath(data);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前被选中的节点所组成的数组
* @method getCheckedNodes
* @param {Boolean} leafOnly 是否只是叶子节点默认false
* @param {Boolean} includeHalfChecked 是否包含半选节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedNodes(leafOnly, includeHalfChecked) {
return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前被选中的节点的 key 所组成的数组
* @method getCheckedKeys
* @param {Boolean} leafOnly 是否只是叶子节点默认false,若为 true 则仅返回被选中的叶子节点的 keys
* @param {Boolean} includeHalfChecked 是否返回indeterminate为true的节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedKeys(leafOnly, includeHalfChecked) {
return this.store.getCheckedKeys(leafOnly, includeHalfChecked);
},
/*
* @description 获取当前被选中节点的 data若没有节点被选中则返回 null
* @method getCurrentNode
* @return {Object} 当前被选中节点的 data若没有节点被选中则返回 null
*/
getCurrentNode() {
const currentNode = this.store.getCurrentNode();
return currentNode ? currentNode.data : null;
},
/*
* @description 获取当前被选中节点的 key若没有节点被选中则返回 null
* @method getCurrentKey
* @return {all} 当前被选中节点的 key 若没有节点被选中则返回 null
*/
getCurrentKey() {
const currentNode = this.getCurrentNode();
return currentNode ? currentNode[this.nodeKey] : null;
},
/*
* @description 设置全选/取消全选
* @method setCheckAll
* @param {Boolean} isCheckAll 选中状态,默认为true
*/
setCheckAll(isCheckAll = true) {
if (this.showRadio) throw new Error(
'You set the "show-radio" property, so you cannot select all nodes');
if (!this.showCheckbox) console.warn(
'You have not set the property "show-checkbox". Please check your settings');
this.store.setCheckAll(isCheckAll);
},
/*
* @description 设置目前勾选的节点
* @method setCheckedNodes
* @param {Array} nodes 接收勾选节点数据的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedNodes(nodes, leafOnly) {
this.store.setCheckedNodes(nodes, leafOnly);
},
/*
* @description 通过 keys 设置目前勾选的节点
* @method setCheckedKeys
* @param {Array} keys 勾选节点的 key 的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedKeys(keys, leafOnly) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
this.store.setCheckedKeys(keys, leafOnly);
this.handleUpdateKey()
},
/*
* @description 通过 key / data 设置某个节点的勾选状态
* @method setChecked
* @param {all} data 勾选节点的 key 或者 data
* @param {Boolean} checked 节点是否选中
* @param {Boolean} deep 是否设置子节点 ,默认为 false
*/
setChecked(data, checked, deep) {
this.store.setChecked(data, checked, deep);
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前半选中的节点所组成的数组
* @method getHalfCheckedNodes
* @return {Array} 目前半选中的节点所组成的数组
*/
getHalfCheckedNodes() {
return this.store.getHalfCheckedNodes();
},
/*
* @description 若节点可被选择(即 show-checkbox 为 true则返回目前半选中的节点的 key 所组成的数组
* @method getHalfCheckedKeys
* @return {Array} 目前半选中的节点的 key 所组成的数组
*/
getHalfCheckedKeys() {
return this.store.getHalfCheckedKeys();
},
/*
* @description 通过 node 设置某个节点的当前选中状态
* @method setCurrentNode
* @param {Object} node 待被选节点的 node
*/
setCurrentNode(node) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
this.store.setUserCurrentNode(node);
},
/*
* @description 通过 key 设置某个节点的当前选中状态
* @method setCurrentKey
* @param {all} key 待被选节点的 key若为 null 则取消当前高亮的节点
*/
setCurrentKey(key) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
this.store.setCurrentNodeKey(key);
},
/*
* @description 根据 data 或者 key 拿到 Tree 组件中的 node
* @method getNode
* @param {all} data 要获得 node 的 key 或者 data
*/
getNode(data) {
return this.store.getNode(data);
},
/*
* @description 删除 Tree 中的一个节点
* @method remove
* @param {all} data 要删除的节点的 data 或者 node
*/
remove(data) {
this.store.remove(data);
},
/*
* @description 为 Tree 中的一个节点追加一个子节点
* @method append
* @param {Object} data 要追加的子节点的 data
* @param {Object} parentNode 子节点的 parent 的 data、key 或者 node
*/
append(data, parentNode) {
this.store.append(data, parentNode);
},
/*
* @description 为 Tree 的一个节点的前面增加一个节点
* @method insertBefore
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的后一个节点的 data、key 或者 node
*/
insertBefore(data, refNode) {
this.store.insertBefore(data, refNode);
},
/*
* @description 为 Tree 的一个节点的后面增加一个节点
* @method insertAfter
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的前一个节点的 data、key 或者 node
*/
insertAfter(data, refNode) {
this.store.insertAfter(data, refNode);
},
/*
* @description 通过 keys 设置节点子元素
* @method updateKeyChildren
* @param {String, Number} key 节点 key
* @param {Object} data 节点数据的数组
*/
updateKeyChildren(key, data) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
this.store.updateChildren(key, data);
}
},
created() {
this.isTree = true;
let props = this.props;
if (typeof this.props === 'function') props = this.props();
if (typeof props !== 'object') throw new Error('props must be of object type.');
this.store = new TreeStore({
key: this.nodeKey,
data: this.treeData,
lazy: this.lazy,
props: props,
load: this.load,
showCheckbox: this.showCheckbox,
showRadio: this.showRadio,
currentNodeKey: this.currentNodeKey,
checkStrictly: this.checkStrictly || this.checkOnlyLeaf,
checkDescendants: this.checkDescendants,
expandOnCheckNode: this.expandOnCheckNode,
defaultCheckedKeys: this.defaultCheckedKeys,
defaultExpandedKeys: this.defaultExpandedKeys,
expandCurrentNodeParent: this.expandCurrentNodeParent,
autoExpandParent: this.autoExpandParent,
defaultExpandAll: this.defaultExpandAll,
filterNodeMethod: this.filterNodeMethod,
childVisibleForFilterNode: this.childVisibleForFilterNode,
showNodeIcon: this.showNodeIcon,
isInjectParentInNode: this.isInjectParentInNode
});
this.childNodesId = this.store.root.childNodesId;
uni.$on(`updateKey`, () => {
this.handleUpdateKey()
this.mathKey++
});
},
beforeDestroy() {
if (this.accordion) {
uni.$off(`${this.elId}-tree-node-expand`)
}
uni.$off('updateKey')
}
};
</script>
<style lang="scss" scoped>
.notData-box {
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
margin-top: 200rpx;
.notData-inner {
width: 286rpx;
height: 222rpx;
align-items: center;
.iconImg {
width: 100%;
height: 100%;
}
}
.notData-inner-text {
color: #909399;
}
}
.ly-tree {
position: relative;
cursor: default;
background: #FFF;
color: #606266;
padding: 30rpx;
}
.ly-tree.is-empty {
background: transparent;
}
/* lyEmpty-start */
.ly-empty {
width: 100%;
display: flex;
justify-content: center;
margin-top: 100rpx;
}
/* lyEmpty-end */
/* lyLoader-start */
.ly-loader {
margin-top: 100rpx;
display: flex;
align-items: center;
justify-content: center;
}
.ly-loader-inner,
.ly-loader-inner:before,
.ly-loader-inner:after {
background: #efefef;
animation: load 1s infinite ease-in-out;
width: .5em;
height: 1em;
}
.ly-loader-inner:before,
.ly-loader-inner:after {
position: absolute;
top: 0;
content: '';
}
.ly-loader-inner:before {
left: -1em;
}
.ly-loader-inner {
text-indent: -9999em;
position: relative;
font-size: 22rpx;
animation-delay: 0.16s;
}
.ly-loader-inner:after {
left: 1em;
animation-delay: 0.32s;
}
/* lyLoader-end */
@keyframes load {
0%,
80%,
100% {
box-shadow: 0 0 #efefef;
height: 1em;
}
40% {
box-shadow: 0 -1.5em #efefef;
height: 1.5em;
}
}
</style>

View File

@@ -0,0 +1,538 @@
import {
markNodeData,
objectAssign,
arrayFindIndex,
getChildState,
reInitChecked,
getPropertyFromData,
isNull,
NODE_KEY
} from '../tool/util';
const getStore = function(store) {
let thisStore = store;
return function() {
return thisStore;
}
}
let nodeIdSeed = 0;
export default class Node {
constructor(options) {
this.time = new Date().getTime();
this.id = nodeIdSeed++;
this.text = null;
this.checked = false;
this.indeterminate = false;
this.data = null;
this.expanded = false;
this.parentId = null;
this.visible = true;
this.isCurrent = false;
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (name === 'store') {
this.store = getStore(options[name]);
} else {
this[name] = options[name];
}
}
}
if (!this.store()) {
throw new Error('[Node]store is required!');
}
// internal
this.level = 0;
this.loaded = false;
this.childNodesId = [];
this.loading = false;
this.label = getPropertyFromData(this, 'label');
this.key = this._getKey();
this.disabled = getPropertyFromData(this, 'disabled');
this.nextSibling = null;
this.previousSibling = null;
this.icon = '';
this._handleParentAndLevel();
this._handleProps();
this._handleExpand();
this._handleCurrent();
if (this.store().lazy) {
this.store()._initDefaultCheckedNode(this);
}
this.updateLeafState();
}
_getKey() {
if (!this.data || Array.isArray(this.data)) return null;
if (typeof this.data === 'object') {
const nodeKey = this.store().key;
const key = this.data[nodeKey];
if (typeof key === 'undefined') {
throw new Error(`您配置的node-key为"${nodeKey}",但数据中并未找到对应"${nodeKey}"属性的值请检查node-key的配置是否合理`)
}
return key;
}
throw new Error('不合法的data数据');
}
_handleParentAndLevel() {
if (this.parentId !== null) {
let parent = this.getParent(this.parentId);
if (this.store().isInjectParentInNode) {
this.parent = parent;
}
// 由于这里做了修改默认第一个对象不会被注册到nodesMap中所以找不到parent会报错所以默认parent的level是0
if (!parent) {
parent = {
level: 0
}
} else {
const parentChildNodes = parent.getChildNodes(parent.childNodesId);
const index = parent.childNodesId.indexOf(this.key);
this.nextSibling = index > -1 ? parentChildNodes[index + 1] : null;
this.previousSibling = index > 0 ? parentChildNodes[index - 1] : null;
}
this.level = parent.level + 1;
}
}
_handleProps() {
const props = this.store().props;
if (this.store().showNodeIcon) {
if (props && typeof props.icon !== 'undefined') {
this.icon = getPropertyFromData(this, 'icon');
} else {
console.warn('请配置props属性中的"icon"字段')
}
}
this.store().registerNode(this);
if (props && typeof props.isLeaf !== 'undefined') {
const isLeaf = getPropertyFromData(this, 'isLeaf');
if (typeof isLeaf === 'boolean') {
this.isLeafByUser = isLeaf;
}
}
}
_handleExpand() {
if (this.store().lazy !== true && this.data) {
this.setData(this.data);
if (this.store().defaultExpandAll) {
this.expanded = true;
}
} else if (this.level > 0 && this.store().lazy && this.store().defaultExpandAll) {
this.expand();
}
if (!Array.isArray(this.data)) {
markNodeData(this, this.data);
}
if (!this.data) return;
const defaultExpandedKeys = this.store().defaultExpandedKeys;
const key = this.store().key;
if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) {
this.expand(null, this.store().autoExpandparent);
}
}
_handleCurrent() {
const key = this.store().key;
if (key && this.store().currentNodeKey !== undefined && this.key === this.store().currentNodeKey) {
this.store().currentNode = this;
this.store().currentNode.isCurrent = true;
}
}
destroyStore() {
getStore(null)
}
setData(data) {
if (!Array.isArray(data)) {
markNodeData(this, data);
}
this.data = data;
this.childNodesId = [];
let children;
if (this.level === 0 && Array.isArray(this.data)) {
children = this.data;
} else {
children = getPropertyFromData(this, 'children') || [];
}
for (let i = 0, j = children.length; i < j; i++) {
this.insertChild({
data: children[i]
});
}
}
contains(target, deep = true) {
const walk = function(parent) {
const children = parent.getChildNodes(parent.childNodesId) || [];
let result = false;
for (let i = 0, j = children.length; i < j; i++) {
const child = children[i];
if (child === target || (deep && walk(child))) {
result = true;
break;
}
}
return result;
};
return walk(this);
}
remove() {
if (this.parentId !== null) {
const parent = this.getParent(this.parentId);
parent.removeChild(this);
}
}
insertChild(child, index, batch) {
if (!child) throw new Error('insertChild error: child is required.');
if (!(child instanceof Node)) {
if (!batch) {
const children = this.getChildren(true);
if (children.indexOf(child.data) === -1) {
if (typeof index === 'undefined' || index < 0) {
children.push(child.data);
} else {
children.splice(index, 0, child.data);
}
}
}
objectAssign(child, {
parentId: isNull(this.key) ? '' : this.key,
store: this.store()
});
child = new Node(child);
}
child.level = this.level + 1;
if (typeof index === 'undefined' || index < 0) {
this.childNodesId.push(child.key);
} else {
this.childNodesId.splice(index, 0, child.key);
}
this.updateLeafState();
}
insertBefore(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
}
this.insertChild(child, index);
}
insertAfter(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
if (index !== -1) index += 1;
}
this.insertChild(child, index);
}
removeChild(child) {
const children = this.getChildren() || [];
const dataIndex = children.indexOf(child.data);
if (dataIndex > -1) {
children.splice(dataIndex, 1);
}
const index = this.childNodesId.indexOf(child.key);
if (index > -1) {
this.store() && this.store().deregisterNode(child);
child.parentId = null;
this.childNodesId.splice(index, 1);
}
this.updateLeafState();
}
removeChildByData(data) {
let targetNode = null;
for (let i = 0; i < this.childNodesId.length; i++) {
let node = this.getChildNodes(this.childNodesId);
if (node[i].data === data) {
targetNode = node[i];
break;
}
}
if (targetNode) {
this.removeChild(targetNode);
}
}
// 为了避免APP端parent嵌套结构导致报错这里parent需要从nodesMap中获取
getParent(parentId) {
try {
if (!parentId.toString()) return null;
return this.store().nodesMap[parentId];
} catch (error) {
return null;
}
}
// 为了避免APP端childNodes嵌套结构导致报错这里childNodes需要从nodesMap中获取
getChildNodes(childNodesId) {
let childNodes = [];
if (childNodesId.length === 0) return childNodes;
childNodesId.forEach((key) => {
childNodes.push(this.store().nodesMap[key]);
})
return childNodes;
}
expand(callback, expandparent) {
const done = () => {
if (expandparent) {
let parent = this.getParent(this.parentId);
while (parent && parent.level > 0) {
parent.expanded = true;
parent = this.getParent(parent.parentId);
}
}
this.expanded = true;
if (callback) callback();
};
if (this.shouldLoadData()) {
this.loadData(function(data) {
if (Array.isArray(data)) {
if (this.checked) {
this.setChecked(true, true);
} else if (!this.store().checkStrictly) {
reInitChecked(this);
}
done();
}
});
} else {
done();
}
}
doCreateChildren(array, defaultProps = {}) {
array.forEach((item) => {
this.insertChild(objectAssign({
data: item
}, defaultProps), undefined, true);
});
}
collapse() {
this.expanded = false;
}
shouldLoadData() {
return this.store().lazy === true && this.store().load && !this.loaded;
}
updateLeafState() {
if (this.store().lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodesId = this.childNodesId;
if (!this.store().lazy || (this.store().lazy === true && this.loaded === true)) {
this.isLeaf = !childNodesId || childNodesId.length === 0;
return;
}
this.isLeaf = false;
}
setChecked(value, deep, recursion, passValue) {
this.indeterminate = value === 'half';
this.checked = value === true;
if (this.checked && this.store().expandOnCheckNode) {
this.expand(null, true)
}
if (this.store().checkStrictly) return;
if (this.store().showRadio) return;
if (!(this.shouldLoadData() && !this.store().checkDescendants)) {
let childNodes = this.getChildNodes(this.childNodesId);
let {
all,
allWithoutDisable
} = getChildState(childNodes);
if (!this.isLeaf && (!all && allWithoutDisable)) {
this.checked = false;
value = false;
}
const handleDescendants = () => {
if (deep) {
let childNodes = this.getChildNodes(this.childNodesId)
for (let i = 0, j = childNodes.length; i < j; i++) {
const child = childNodes[i];
passValue = passValue || value !== false;
const isCheck = child.disabled ? child.checked : passValue;
child.setChecked(isCheck, deep, true, passValue);
}
const {
half,
all
} = getChildState(childNodes);
if (!all) {
this.checked = all;
this.indeterminate = half;
}
}
};
if (this.shouldLoadData()) {
this.loadData(() => {
handleDescendants();
reInitChecked(this);
}, {
checked: value !== false
});
return;
} else {
handleDescendants();
}
}
if (!this.parentId) return;
let parent = this.getParent(this.parentId);
if (parent && parent.level === 0) return;
if (!recursion) {
reInitChecked(parent);
}
}
setRadioChecked(value) {
const allNodes = this.store()._getAllNodes().sort((a, b) => b.level - a.level);
allNodes.forEach(node => node.setChecked(false, false));
this.checked = value === true;
}
getChildren(forceInit = false) {
if (this.level === 0) return this.data;
const data = this.data;
if (!data) return null;
const props = this.store().props;
let children = 'children';
if (props) {
children = props.children || 'children';
}
if (data[children] === undefined) {
data[children] = null;
}
if (forceInit && !data[children]) {
data[children] = [];
}
return data[children];
}
updateChildren() {
let childNodes = this.getChildNodes(this.childNodesId);
const newData = this.getChildren() || [];
const oldData = childNodes.map((node) => node.data);
const newDataMap = {};
const newNodes = [];
newData.forEach((item, index) => {
const key = item[NODE_KEY];
const isNodeExists = !!key && arrayFindIndex(oldData, data => data[NODE_KEY] === key) >= 0;
if (isNodeExists) {
newDataMap[key] = {
index,
data: item
};
} else {
newNodes.push({
index,
data: item
});
}
});
if (!this.store().lazy) {
oldData.forEach((item) => {
if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item);
});
}
newNodes.forEach(({
index,
data
}) => {
this.insertChild({
data
}, index);
});
this.updateLeafState();
}
loadData(callback, defaultProps = {}) {
if (this.store().lazy === true &&
this.store().load && !this.loaded &&
(!this.loading || Object.keys(defaultProps).length)
) {
this.loading = true;
const resolve = (children) => {
this.loaded = true;
this.loading = false;
this.childNodesId = [];
this.doCreateChildren(children, defaultProps);
this.updateLeafState();
callback && callback.call(this, children);
};
this.store().load(this, resolve);
} else {
callback && callback.call(this);
}
}
}

View File

@@ -0,0 +1,428 @@
import Node from './node';
import {
getNodeKey,
getPropertyFromData
} from '../tool/util';
export default class TreeStore {
constructor(options) {
this.ready = false;
this.currentNode = null;
this.currentNodeKey = null;
Object.assign(this, options);
if (!this.key) {
throw new Error('[Tree] nodeKey is required');
}
this.nodesMap = {};
this.root = new Node({
data: this.data,
store: this
});
if (this.lazy && this.load) {
const loadFn = this.load;
loadFn(this.root, (data) => {
this.root.doCreateChildren(data);
this._initDefaultCheckedNodes();
this.ready = true;
uni.$emit('updateKey') //加了异步才会展示数据
});
} else {
this._initDefaultCheckedNodes();
this.ready = true;
}
}
getReady() {
return this.ready
}
filter(value, data) {
const filterNodeMethod = this.filterNodeMethod;
const lazy = this.lazy;
const _self = this;
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if (data && typeof data === 'object') {
let nodePath = _self.getNodePath(child.data);
if (!nodePath.some(pathItem => pathItem[_self.key] === data[_self.key])) {
child.visible = false;
traverse(child);
return;
}
}
if (_self.childVisibleForFilterNode) {
let parent = child.getParent(child.parentId);
child.visible = filterNodeMethod.call(child, value, child.data, child) || (parent &&
parent.visible);
} else {
child.visible = filterNodeMethod.call(child, value, child.data, child);
}
traverse(child);
});
if (!node.visible && childNodes.length) {
let allHidden = true;
allHidden = !childNodes.some(child => child.visible);
if (node.root) {
node.root.visible = allHidden === false;
} else {
node.visible = allHidden === false;
}
}
if (!value) return;
if (node.visible && !node.isLeaf && !lazy) node.expand();
};
traverse(this);
}
setData(newVal) {
const instanceChanged = newVal !== this.root.data;
if (instanceChanged) {
this.root.setData(newVal);
this._initDefaultCheckedNodes();
} else {
this.root.updateChildren();
}
}
getNode(data) {
if (data instanceof Node) return data;
const key = typeof data !== 'object' ? data : getNodeKey(this.key, data);
if (!key) return null;
return this.nodesMap[key] || null;
}
insertBefore(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertBefore({
data
}, refNode);
}
insertAfter(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertAfter({
data
}, refNode);
}
remove(data) {
const node = this.getNode(data);
if (node && node.parentId !== null) {
let parent = node.getParent(node.parentId);
if (node === this.currentNode) {
this.currentNode = null;
}
parent.removeChild(node);
}
}
append(data, parentData) {
const parentNode = parentData ? this.getNode(parentData) : this.root;
if (parentNode) {
parentNode.insertChild({
data
});
}
}
_initDefaultCheckedNodes() {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
const nodesMap = this.nodesMap;
let checkedKeyfromData = [];
let totalCheckedKeys = []
for (let key in nodesMap) {
let checked = getPropertyFromData(nodesMap[key], 'checked') || false;
checked && checkedKeyfromData.push(key);
}
totalCheckedKeys = Array.from(new Set([...defaultCheckedKeys, ...checkedKeyfromData]));
totalCheckedKeys.forEach((checkedKey) => {
const node = nodesMap[checkedKey];
if (node) {
node.setChecked(true, !this.checkStrictly);
}
});
}
_initDefaultCheckedNode(node) {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
if (defaultCheckedKeys.indexOf(node.key) !== -1) {
node.setChecked(true, !this.checkStrictly);
}
}
toggleExpendAll(isExpandAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
const node = this.getNode(item.key);
if (node) isExpandAll ? node.expand() : node.collapse();
});
}
setCheckAll(isCkeckAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
item.setChecked(isCkeckAll, false);
});
}
setDefaultCheckedKey(newVal) {
if (newVal !== this.defaultCheckedKeys) {
this.defaultCheckedKeys = newVal;
this._initDefaultCheckedNodes();
}
}
registerNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
const nodeKey = node.key;
if (nodeKey !== undefined) this.nodesMap[node.key] = node;
}
deregisterNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
let childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach(child => {
this.deregisterNode(child);
});
delete this.nodesMap[node.key];
}
getNodePath(data) {
if (!this.key) throw new Error('[Tree] nodeKey is required in getNodePath');
const node = this.getNode(data);
if (!node) return [];
const path = [node.data];
let parent = node.getParent(node.parentId);
while (parent && parent !== this.root) {
path.push(parent.data);
parent = parent.getParent(parent.parentId);
}
return path.reverse();
}
getCheckedNodes(leafOnly = false, includeHalfChecked = false) {
const checkedNodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if ((child.checked || (includeHalfChecked && child.indeterminate)) && (!leafOnly || (
leafOnly && child.isLeaf))) {
checkedNodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return checkedNodes;
}
getCheckedKeys(leafOnly = false, includeHalfChecked = false) {
return this.getCheckedNodes(leafOnly, includeHalfChecked).map((data) => (data || {})[this.key]);
}
getHalfCheckedNodes() {
const nodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(
node.childNodesId);
childNodes.forEach((child) => {
if (child.indeterminate) {
nodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return nodes;
}
getHalfCheckedKeys() {
return this.getHalfCheckedNodes().map((data) => (data || {})[this.key]);
}
_getAllNodes() {
const allNodes = [];
const nodesMap = this.nodesMap;
for (let nodeKey in nodesMap) {
if (nodesMap.hasOwnProperty(nodeKey)) {
allNodes.push(nodesMap[nodeKey]);
}
}
return allNodes;
}
updateChildren(key, data) {
const node = this.nodesMap[key];
if (!node) return;
const childNodes = node.getChildNodes(node.childNodesId);
for (let i = childNodes.length - 1; i >= 0; i--) {
const child = childNodes[i];
this.remove(child.data);
}
for (let i = 0, j = data.length; i < j; i++) {
const child = data[i];
this.append(child, node.data);
}
}
_setCheckedKeys(key, leafOnly = false, checkedKeys) {
const allNodes = this._getAllNodes().sort((a, b) => b.level - a.level);
const cache = Object.create(null);
const keys = Object.keys(checkedKeys);
allNodes.forEach(node => node.setChecked(false, false));
for (let i = 0, j = allNodes.length; i < j; i++) {
const node = allNodes[i];
let nodeKey = node.data[key];
if (typeof nodeKey === 'undefined') continue;
nodeKey = nodeKey.toString();
let checked = keys.indexOf(nodeKey) > -1;
if (!checked) {
if (node.checked && !cache[nodeKey]) {
node.setChecked(false, false);
}
continue;
}
let parent = node.getParent(node.parentId);
while (parent && parent.level > 0) {
cache[parent.data[key]] = true;
parent = parent.getParent(parent.parentId);
}
if (node.isLeaf || this.checkStrictly) {
node.setChecked(true, false);
continue;
}
node.setChecked(true, true);
if (leafOnly) {
node.setChecked(false, false);
const traverse = function(node) {
const childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if (!child.isLeaf) {
child.setChecked(false, false);
}
traverse(child);
});
};
traverse(node);
}
}
}
setCheckedNodes(array, leafOnly = false) {
const key = this.key;
const checkedKeys = {};
array.forEach((item) => {
checkedKeys[(item || {})[key]] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setCheckedKeys(keys, leafOnly = false) {
this.defaultCheckedKeys = keys;
const key = this.key;
const checkedKeys = {};
keys.forEach((key) => {
checkedKeys[key] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setDefaultExpandedKeys(keys) {
keys = keys || [];
this.defaultExpandedKeys = keys;
keys.forEach((key) => {
const node = this.getNode(key);
if (node) node.expand(null, this.autoExpandParent);
});
}
setChecked(data, checked, deep) {
const node = this.getNode(data);
if (node) {
node.setChecked(!!checked, deep);
}
}
getCurrentNode() {
return this.currentNode;
}
setCurrentNode(currentNode) {
const prevCurrentNode = this.currentNode;
if (prevCurrentNode) {
prevCurrentNode.isCurrent = false;
}
this.currentNode = currentNode;
this.currentNode.isCurrent = true;
this.expandCurrentNodeParent && this.currentNode.expand(null, true)
}
setUserCurrentNode(node) {
const key = node[this.key];
const currNode = this.nodesMap[key];
this.setCurrentNode(currNode);
}
setCurrentNodeKey(key) {
if (key === null || key === undefined) {
this.currentNode && (this.currentNode.isCurrent = false);
this.currentNode = null;
return;
}
const node = this.getNode(key);
if (node) {
this.setCurrentNode(node);
}
}
};

View File

@@ -0,0 +1,115 @@
export const NODE_KEY = '$treeNodeId';
export const markNodeData = function(node, data) {
if (!data || data[NODE_KEY]) return;
Object.defineProperty(data, NODE_KEY, {
value: node.id,
enumerable: false,
configurable: false,
writable: false
});
};
export const getNodeKey = function(key, data) {
if (!data) return null;
if (!key) return data[NODE_KEY];
return data[key];
};
export const objectAssign = function(target) {
for (let i = 1, j = arguments.length; i < j; i++) {
let source = arguments[i] || {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
let value = source[prop];
if (value !== undefined) {
target[prop] = value;
}
}
}
}
return target;
};
// TODO: use native Array.find, Array.findIndex when IE support is dropped
export const arrayFindIndex = function(arr, pred) {
for (let i = 0; i !== arr.length; ++i) {
if (pred(arr[i])) {
return i;
}
}
return -1;
};
export const getChildState = function(node) {
let all = true;
let none = true;
let allWithoutDisable = true;
for (let i = 0, j = node.length; i < j; i++) {
const n = node[i];
if (n.checked !== true || n.indeterminate) {
all = false;
if (!n.disabled) {
allWithoutDisable = false;
}
}
if (n.checked !== false || n.indeterminate) {
none = false;
}
}
return {
all,
none,
allWithoutDisable,
half: !all && !none
};
};
export const reInitChecked = function(node) {
if (!node || node.childNodesId.length === 0) return;
let childNodes = node.getChildNodes(node.childNodesId);
const {
all,
none,
half
} = getChildState(childNodes);
if (all) {
node.checked = true;
node.indeterminate = false;
} else if (half) {
node.checked = false;
node.indeterminate = true;
} else if (none) {
node.checked = false;
node.indeterminate = false;
}
let parent = node.getParent(node.parentId);
if (!parent || parent.level === 0) return;
if (!node.store().checkStrictly) {
reInitChecked(parent);
}
};
export const getPropertyFromData = function(node, prop) {
const props = node.store().props;
const data = node.data || {};
const config = props[prop];
if (typeof config === 'function') {
return config(data, node);
} else if (typeof config === 'string') {
return data[config];
} else if (typeof config === 'undefined') {
const dataProp = data[prop];
return dataProp === undefined ? '' : dataProp;
}
};
export const isNull = function(v) {
return v === undefined || v === null || v === '';
}