初始提交
This commit is contained in:
102
components/treeCollapse/index.vue
Normal file
102
components/treeCollapse/index.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<u-popup v-model="showApply" :mode="mode" :height="height" @close="close" :width="width"
|
||||
:custom-style="customStyle">
|
||||
<slot></slot>
|
||||
<view class="tree-main">
|
||||
<ly-tree :props="prop" :node-key="prop.value" :tree-data="treeData" show-node-icon :defaultExpandAll='true'
|
||||
ref="tree" @node-click="handleNodeClick" :highlight-current="true" />
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LyTree from './ly-tree.vue'
|
||||
let _self;
|
||||
export default {
|
||||
components: {
|
||||
LyTree
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
treeData: Array,
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '600rpx'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '400rpx'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: uni.getSystemInfoSync().statusBarHeight + 'px',
|
||||
showApply: false,
|
||||
isReady: false,
|
||||
prop: {
|
||||
label: 'fullName',
|
||||
isLeaf: 'isLeaf',
|
||||
value: "id",
|
||||
icon: 'avatar'
|
||||
},
|
||||
customStyle: {
|
||||
// #ifdef MP
|
||||
'margin-top': '160rpx'
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
'margin-top': '80rpx'
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
this.showApply = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
_self = this
|
||||
this.isReady = true;
|
||||
},
|
||||
computed: {
|
||||
baseURL() {
|
||||
return this.define.baseURL
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNodeClick(obj) {
|
||||
if (obj.data.type != 1) this.$emit('change', obj.data)
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
page {
|
||||
padding-top: 80rpx;
|
||||
}
|
||||
|
||||
.u-drawer {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.ly-tree {
|
||||
/* padding: 20rpx !important; */
|
||||
}
|
||||
</style>
|
||||
377
components/treeCollapse/ly-tree-node.vue
Normal file
377
components/treeCollapse/ly-tree-node.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<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'
|
||||
}">
|
||||
<view class="">
|
||||
|
||||
<text :class="node.data.icon" class="u-m-r-12"></text>
|
||||
<text class="ly-tree-node__label u-line-1">{{node.label}}</text>
|
||||
</view>
|
||||
<view class="" @tap.stop="handleExpandIconClick">
|
||||
<u-icon :name="iconName" v-if="node.data.hasChildren"></u-icon>
|
||||
</view>
|
||||
</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';
|
||||
export default {
|
||||
name: 'LyTreeNode',
|
||||
componentName: 'LyTreeNode',
|
||||
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 {
|
||||
iconName: 'arrow-down',
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
this.iconName = 'arrow-down'
|
||||
} else {
|
||||
this.node.expand();
|
||||
this.iconName = 'arrow-up'
|
||||
// this.tree.$emit('node-expand', this.node);
|
||||
|
||||
if (this.tree.accordion) {
|
||||
// uni.$emit(`${this.tree.elId}-tree-node-expand`, this.node);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleCheckChange(checked) {
|
||||
if (this.node.disabled) return;
|
||||
|
||||
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;
|
||||
margin-bottom: 20rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ly-tree-node__label_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.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__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>
|
||||
625
components/treeCollapse/ly-tree.vue
Normal file
625
components/treeCollapse/ly-tree.vue
Normal file
@@ -0,0 +1,625 @@
|
||||
<template>
|
||||
<view>
|
||||
<template v-if="showLoading">
|
||||
<view class="ly-loader ly-flex-center">
|
||||
<view class="ly-loader-inner">加载中...</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<view v-if="isEmpty || !visible" class="ly-empty">
|
||||
{{emptyText}}
|
||||
</view>
|
||||
<view :key="updateKey" class="ly-tree" :class="{'is-empty': isEmpty || !visible}" role="tree"
|
||||
name="LyTreeExpand">
|
||||
<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" updateKey="hanldeUpdateKey">
|
||||
</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
|
||||
};
|
||||
},
|
||||
|
||||
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() {
|
||||
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() {
|
||||
this.updateKey = new Date().getTime();
|
||||
},
|
||||
|
||||
/*
|
||||
* @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>
|
||||
.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;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
margin-top: 250rpx;
|
||||
/* #endif */
|
||||
/* #ifndef MP-WEIXIN */
|
||||
margin-top: 100rpx;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* 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>
|
||||
538
components/treeCollapse/model/node.js
Normal file
538
components/treeCollapse/model/node.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
428
components/treeCollapse/model/tree-store.js
Normal file
428
components/treeCollapse/model/tree-store.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
115
components/treeCollapse/tool/util.js
Normal file
115
components/treeCollapse/tool/util.js
Normal 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 === '';
|
||||
}
|
||||
Reference in New Issue
Block a user