qa-regulatory-gwj-app/uni_modules/next-tree/components/next-tree/next-tree.vue

1044 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="next-tree">
<view class="next-tree-mask" v-if="uiMode === 'popup'" :class="{'show':showTree}" @tap="_cancel"></view>
<view class="next-tree-cnt" :style="{top: top, height: pageHeight}" :class="{'show':showTree, 'next-tree-cnt-page': uiMode === 'page'}">
<view v-if="_showTreeBar" class="next-tree-bar">
<view class="next-tree-bar-cancel" :style="{'color':cancelColor}" hover-class="hover-c" @tap="_cancel">取消</view>
<view class="next-tree-bar-title" :style="{'color':titleColor}">{{customTitle}}</view>
<view class="next-tree-bar-btns">
<view v-if="['checkbox', 'radio'].indexOf(funcMode) !== -1" class="next-tree-bar-cancel" :style="{'color':cancelColor}" hover-class="hover-c" @tap="_clear">清空</view>
<view v-else-if="funcMode === 'edit'" class="next-tree-bar-cancel" :style="{'color':cancelColor}" hover-class="hover-c" @tap="_revert">还原</view>
<view class="btn-divid"></view>
<view class="next-tree-bar-confirm" :style="{'color':_themeColor}" hover-class="hover-c" @tap="_confirm">确定</view>
</view>
</view>
<!-- #ifndef H5-->
<view class="next-tree-view" :style="'top:'+(_showTreeBar?'72rpx':'0rpx')">
<!-- #endif-->
<!-- #ifdef H5-->
<view class="next-tree-view" style="top: 72rpx">
<!-- #endif-->
<next-search-more v-if="ifSearch" @search="onSearch" mode="center" placeholder="请输入关键字" :isFixedSearchBtn="false" />
<slot name="topBar"></slot>
<scroll-view class="next-tree-view-sc" :scroll-y="true">
<view v-if="_treeList.length">
<block v-for="(item, index) in _treeList" :key="index">
<view class="next-tree-item-block" v-if="item.show">
<view class="next-tree-item" :style="[{
paddingLeft: item.rank*15 + 'px',
zIndex: item.rank*-1 +50
}]"
:class="{
border: border === true,
show: item.show,
last: item.lastRank,
showchild: item.showChild,
open: item.open,
disabled: item.disabled === true
}">
<block v-if="showAuxiliaryLine">
<template v-if="item.rank > 1">
<view :key="i" v-for="i in (item.rank - 1)" :style="{left: (6 * (2*i - 1) + 3 * (i - 1)) + 'px'}" class="parent-horizontal-line"></view>
</template>
<view class="left-line">
<view v-if="item.lastRank" class="horizontal-line"></view>
</view>
</block>
<view class="next-tree-label" @tap.stop="_treeItemTap(item, index)">
<image class="next-tree-icon" :src="item.lastRank ? lastIcon : item.showChild ? currentIcon : defaultIcon"></image>
<input @click.stop @tap.stop class="label-input" placeholder="请输入" v-if="funcMode === 'edit' && item.status === 'edit'" v-model="item.name" />
<rich-text :nodes="getNodes(item.ouputText)" :selectable="false" v-else-if="ifSearch && searchModel==='depHighlight' && keywords"></rich-text>
<slot v-else-if="$slots.label" name="label" :data="_getLabelSlotData(item)"></slot>
<rich-text v-else-if="item.checked && !item.disabled" :nodes="getThemeNodes(item.name)"></rich-text>
<text v-else>{{item.name}}</text>
</view>
<template v-if="['checkbox', 'radio'].indexOf(funcMode) !== -1">
<view class="next-tree-check" @tap.stop="_treeItemSelect(item, index)" v-if="selectParent === true ? true : item.lastRank">
<view class="next-tree-check-yes" v-if="item.checked" :class="{'radio':!multiple}" :style="{'border-color': item.disabled ? '#ccc' : _themeColor, 'background-color': item.disabled ? '#ccc' : _themeColor}">
<view class="next-tree-check-yes-b" :style="{'background-color':item.disabled ? '#ccc' : _themeColor}">
<text v-if="item.checked" class="icon-text">✔</text>
</view>
</view>
<view class="next-tree-check-no" v-else :class="{'radio':!multiple}" :style="{'border-color': item.disabled ? '#ccc' : _themeColor}">
<text v-if="showHalfChecked(item) && showHalfCheckedTips" :style="{'color': item.disabled ? '#ccc' : _themeColor, 'font-weight': 'blod', 'font-size': '10px'}" class="icon-text">一</text>
</view>
</view>
</template>
<template v-else-if="funcMode === 'edit'">
<text v-if="item.status === 'loading'" :style="{'color': '#ccc'}" class="iconfont-loading icon-btn">&#xe629;</text>
<view v-else>
<text @click="_complete(item)" v-if="item.status === 'edit'" :style="{'color': item.disabled ? '#ccc' : _themeColor}" class="iconfont icon-btn">&#xe6cf;</text>
<template v-else>
<text @click="_editItem(item)" :style="{'color': item.disabled ? '#ccc' : _themeColor}" class="iconfont icon-btn">&#xe66e;</text>
<text @click="_addSubItem(item)" :style="{'color': item.disabled ? '#ccc' : '#333'}" class="iconfont icon-btn">&#xe664;</text>
<text @click="_addSameItem(item)" :style="{'color': item.disabled ? '#ccc' : '#333'}" class="iconfont icon-btn">&#xe601;</text>
</template>
<text @click="_delItem(item)" :style="{'color': item.disabled ? '#ccc' : '#ff4d4f'}" class="iconfont icon-btn">&#xe602;</text>
</view>
</template>
</view>
</view>
</block>
</view>
<view v-else>
<slot v-if="$slots.empty" name="empty"></slot>
<view class="empty" v-else><text>暂无数据</text></view>
</view>
<view v-if="ifSearch" style="height: 80rpx"></view>
<slot name="bottomBar"></slot>
</scroll-view>
</view>
</view>
<view class="fixed-bottom-bar"><slot name="fixedBottomBar"></slot></view>
</view>
</template>
<script>
export default {
name: "next-tree",
props: {
uiMode: {
type: String,
default: 'popup' // popup(弹窗), page(页面)
},
funcMode: {
type: String,
default: 'radio' // display(展示模式), edit(编辑模式), checkbox(多选模式), radio(单选模式)
},
treeData: {
type: Array,
default: function() {
return []
}
},
valueKey: {
type: String,
default: 'id'
},
labelKey: {
type: String,
default: 'label'
},
disabledKey: {
type: String,
default: 'disabled'
},
childrenKey: {
type: String,
default: 'children'
},
title: {
type: [String, Function],
default: ''
},
selectParent: { //是否可以选父级
type: Boolean,
default: false
},
foldAll: { //折叠时关闭所有已经打开的子集,再次打开时需要一级一级打开
type: Boolean,
default: false
},
themeColor: { // 主题颜色
type: String,
default: '#f9ae3d' // #f9ae3d
},
cancelColor: { // 取消按钮颜色
type: String,
default: '' // #757575
},
titleColor: { // 标题颜色
type: String,
default: '' // #757575
},
currentIcon: { // 展开时候的ic
type: String,
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEQ0QTM0MzQ1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEQ0QTM0MzU1Q0RBMTFFOUE0MjY4NzI1Njc1RjI1ODIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowRDRBMzQzMjVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowRDRBMzQzMzVDREExMUU5QTQyNjg3MjU2NzVGMjU4MiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PidwepsAAAK0SURBVHja7JxbTsJAFIYHww7ciStgCeoGvGxAiOsgURegoL5720AXYLiIr0aJviq3Zx3PhIEnKG3ndtr+f3KixrSUj/ZjzjClIqUUiFm2gAAQAREQEUAEREAERAQQAREQAREBREAEREBEEqa67h9RFDWllDv0awWYlqlQHmu1WjMRRMoV1QFttA12y3xRtdNczq8EsE4/f8FumX2q77ROvNXk8UGMEKdUz6tYJHljaZAbuyUH+UR1to5BEohTuqwPCeS4pAA/qY6o/kyHOAMCeRK3owJnj+rH1jjxhqpVsstaebCz6TmnHWyXyY+xHjSBWBY/bvSgadtXBj9u9KCN3rnIfkzkQVsTEEX0Y2IP2oKo/HhMICcFAThUcwVZNGU6FdbX/XURzkbVF4+ybGhjPrFdgP66QdXNurGtSdk6Xdb9nAJ8oDo3OQlsQZzkdPw41ONBo6vI5scDefRjZg+6gpg3Pxp50CXEvPjR2IOuIXL3oxUPuobI3Y9WPOgDIlc/WvOgL4iL/vqFCcD7LH0xB4hj7cfQ/fWH9qCT+FhG0tN+DBk1PzjOM0SVllixcsBT1AvYc/kAPhc0hRg/3uvxoCgKRN9+dOrBUBB9+9GpB0NC9OVH5x4MDdG1H714kANEV3705kEOEBf9dcPi/lQnsuvLg1wgSu3Ha0v7Uh4MMgUXeuG71H407a+VBy9CPQkOdw+MtB+nGbd/D+FBbhBNxo9SjwcngJjNj0E9yBFiFj8G9SBXiGn8GNyDnCEm8SMLD3KHGOdHNh7kDjHOj2w8mAeIi/5arX+c6b/fxHz9oADEdGdjR/fXCw/OOB5oVfCOgnepz8IB14PMw03jCmTE+QBx5z0gAmKSqK9OUF+hcAeIhu/QYr4Qie8rjW83hhMBERARQAREQAREBBABERCLnH8BBgA+TQI7U4t53AAAAABJRU5ErkJggg=='
},
defaultIcon: { // 折叠时候的ic
type: String,
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFEAAABRCAYAAACqj0o2AAACE0lEQVR4Xu3c200DMRCF4XEltJAOkEugA+ggpUAHoQMqiFMCdEAJUMEiS4mEELlIO7bPOeN9i6K1rG/952myyea1WiCtXmEuYBPR4RBMxInoIOCwhOtJLKVszWyXc/5y2BvNEq6I+/3+kFK6M7OHnPM7jcLKjbZAvD/uaZtzflm5P4rbWyJWgDcze1LPuzVihfxUz7sH4ilJ2bx7Isrm3RtRMu8RiHJ5j0SUyXs0okTeCIj0eSMh0uaNhkiZNyIiXd7IiDR5oyNS5M2ACJ83EyJs3myIkHkzIsLlzYwIkzc7IkTeCojD81ZCHJa3GuKQvBURu+etjNgtb3XELnlHQGyedyTEZnlHQ2ySd0RE97wjI7rlHR3RJe+JeIrbLOecD6ePpZQ6W1kn2epo4MUrPOKyLN8ppYq1+y1VStncOjIdGnFZlo+U0uOtWOeOY2TE12Ouq//pEA7xXL7XfvcufR8K0Svfv6CREN3yDYfYIt9QiK3yjYTYLF95xB75SiP2ylcZsVu+cogj8pVCHJWvEuKwfOkREfKlRkTJlxkRJl86RMR8qRBR82VChM0XHpEhX2hElnyREWnyhUNkzBcKkTVfJETafIcjKuQ7FFEl35GIMvl2R1TMtyuiar49EWXzbY5oZpv/hibXTF2h3+s60FRKeT6+3TjMS3nrA3ZFRD8xrfY3ER1kJ+JEdBBwWGKeRAfEH1wS5WFZSDB/AAAAAElFTkSuQmCC'
},
lastIcon: { // 没有子集的ic
type: String,
default: ''
},
border: { // 是否有分割线
type: Boolean,
default: false
},
checkStrictly: { // 只有在funcMode为checkbox状态下生效 状态下节点选择完全受控(父子节点选中状态不再关联)
type: Boolean,
default: false
},
checkStrictlyModel: { // 关联模式 weak: 弱关联strong: 强关联
type: String,
default: 'weak'
},
showHalfCheckedTips: { // 只有在funcMode为checkbox, checkStrictly为false状态下生效 父子节点选中状态不再关联,显示半选提示
type: Boolean,
default: true
},
ifSearch: { // 是否开启search模式
type:Boolean,
default: true
},
searchModel: { // 搜索模式配置
type: String,
default:'common' // depHighlight: 从属高亮common: 一般remote远程
},
showAuxiliaryLine: { // 辅助线模式
type:Boolean,
default: false
},
loadData: {
type: Function
},
height: {
type: Number,
default: 500
},
changeVerify: {
type: Function
},
expandedKeys: { // (Controlled) Specifies the keys of the expanded treeNodes
type: Array,
default: () => ([])
},
expandedMode: { // common: 一般模式singe: 单一模式;
type: String,
default: 'common'
},
pageHeight:{
type: String,
default: '100%'
},
},
data() {
return {
showTree: false,
treeList: [],
currentTreeData: [],
selectIndex: -1,
keywords: '',
nodeInitContrl: {},
top: '',
initNum: 1
}
},
computed: {
_showTreeBar() {
return this.uiMode === 'popup'
},
_themeColor() {
return this.themeColor || '#f9ae3d'
},
_treeList () {
if(this.ifSearch && this.keywords) {
return this.treeList.filter(item => {
return (item.name && item.name.indexOf(this.keywords) !== -1)
}).map(item => {
const o = JSON.parse(JSON.stringify(item));
if(o.showChild === false) {
o.showChild = true;
}
if(o.show === false) {
o.show = true;
}
return o
})
} else {
return this.treeList
}
},
customTitle() {
if(typeof this.title === 'function') {
return this.title(this._getCheckedParams());
} else {
return this.title
}
},
multiple() {
if(this.funcMode === 'checkbox') {
return true
} else if(this.funcMode === 'radio') {
return false
} else {
return true
}
}
},
methods: {
_show() {
this.showTree = true
},
_hide() {
this.showTree = false
},
_cancel() {
this._hide()
this.$emit("cancel", '');
},
_confirm() {
// 处理所选数据
let rt = this._getCheckedParams();
this._hide()
this.$emit("confirm", rt);
},
_getLabelSlotData(item) {
const _it = this.getItemFromTreeData(this.currentTreeData, item.id);
const it = Object.assign({}, _it);
delete it[this.childrenKey];
return it
},
_getCheckedParams() {
// 处理所选数据
let rt = [],
obj = {};
this.treeList.forEach((v, i) => {
if (this.treeList[i].checked) {
obj = {}
obj.parents = this.treeList[i].parents
obj = Object.assign(obj, this.treeList[i].source)
// 移除子元素
// delete obj.children
rt.push(obj)
}
});
return rt;
},
_addSubItem(item) {
if(item.disabled) return;
const it = Object.assign({}, item);
if(item.lastRank) {
item.lastRank = false;
item.showChild = true;
}
this.$nextTick(() => {
this.initNum++;
const parentId = item.parentId.concat([]);
const parents = item.parents.concat([]);
parentId.push(item.id);
parents.push(item);
it.disabled = false;
it.rank = it.rank + 1;
it.id = `next-tree-${this.initNum}`;
it.parentId = [...parentId];
it.parents = [...parents];
it.show = true;
it.showChild = false;
it.open = false;
it.name = '';
it.status = 'edit';
it.source = {};
it.hideArr = [];
it.lastRank = true;
let index = -1;
for (let i = this.treeList.length - 1; i >= 0; i--) {
if(this.treeList[i].id === item.id) {
index = i;
break;
}
}
if(index !== -1) {
this.treeList.splice(index + 1, 0, it);
}
})
},
_addSameItem(item) {
if(item.disabled) return;
const it = Object.assign({}, item);
let index = -1;
for (let i = this.treeList.length - 1; i >= 0; i--) {
if(this.treeList[i].id === item.id || this.treeList[i].parentId.indexOf(item.id) !== -1) {
index = i;
break;
}
}
this.initNum++;
it.id = `next-tree-${this.initNum}`;
it.source = {};
it.hideArr = [];
it.name = "";
it.status = 'edit';
if(index !== -1) {
this.treeList.splice(index + 1, 0, it);
}
},
async _complete(item) {
if (item.name) {
item.status = 'loading';
if(item.operateCallback) {
await item.operateCallback('complete', item);
await this.$nextTick();
}
item.status = '';
} else {
uni.showToast({
title: '请先完善内容',
icon: 'none'
})
}
},
_editItem(item) {
if(item.disabled) return;
item.status = 'edit';
},
_revert() {
this.uiModeInit();
},
async _delItem(item) {
if(item.disabled) return;
item.status = 'loading';
if(item.operateCallback) {
await item.operateCallback('delete', item);
await this.$nextTick();
}
this.delItemFunc(item);
},
delItemFunc(item) {
const id = item && item.id ? item.id : '';
const ids = [];
this.treeList.map((it, index) => {
if(it.id === id) {
ids.push(index);
} else if(it.parentId.indexOf(id) !== -1) {
ids.push(index);
}
});
ids.sort((a, b) => b - a);
ids.forEach(index => {
this.treeList.splice(index, 1);
});
},
checkedFunc(values, state = true) {
if(values instanceof Array) {
values.map(id => {
const item = this.treeList.find(it => it.id === id);
if(item) {
item.checked = !!state
}
})
} else {
const _item = this.treeList.find(it => it.id === values);
if(_item) {
_item.checked = !!state
}
}
},
getRenderTreeList(list = [], rank = 0, parentId = [], parents = []) {
const treeList = [];
list.forEach(item => {
const halfChecked = this.getHalfCheckedFormTreeData(item);
let ouputText = '';
if(this.searchModel === 'depHighlight') {
if(parents && parents.length) {
ouputText = parents.map(item => item[this.labelKey]).join(' > ');
ouputText = ouputText + ' > ' + item[this.labelKey];
} else {
ouputText = item[this.labelKey];
}
}
const bool1 = this.expandedKeys.indexOf(item[this.valueKey]) !== -1;
const len = parentId.length;
const bool2 = len > 0 ? this.expandedKeys.indexOf(parentId[len - 1]) !== -1 : bool1;
treeList.push({
id: item[this.valueKey],
name: item[this.labelKey],
source: item,
parentId, // 父级id数组
parents, // 父级id数组
rank, // 层级
showChild: bool1, //子级是否显示
open: bool1, //是否打开
show: (bool1 || bool2) || rank === 0, // 自身是否显示
hideArr: [],
ouputText,
orChecked: item.checked ? item.checked : false,
checked: item.checked ? item.checked : false,
halfChecked,
disabled: this.disabledKey && item[this.disabledKey] === true,
status: '',
operateCallback: (typeof item.operateCallback) === 'function' ? item.operateCallback : undefined
})
if(bool1) {
this.nodeInitContrl[item[this.valueKey]] = true;
} else {
this.nodeInitContrl[item[this.valueKey]] = undefined;
}
if (
(Array.isArray(item[this.childrenKey]) && item[this.childrenKey].length > 0) ||
(this.loadData && Array.isArray(item[this.childrenKey]) &&item[this.childrenKey].length === 0)
) {
let parentid = [...parentId],
parentArr = [...parents],
childrenid = [];
delete parentArr.children
parentid.push(item[this.valueKey]);
parentArr.push({
[this.valueKey]: item[this.valueKey],
[this.labelKey]: item[this.labelKey],
})
} else if(item[this.childrenKey] === null) {
treeList[treeList.length - 1].lastRank = false;
} else {
treeList[treeList.length - 1].lastRank = true;
}
})
return treeList;
},
//扁平化树结构
_renderTreeList(list = [], rank = 0, parentId = [], parents = []) {
list.forEach(item => {
const halfChecked = this.getHalfCheckedFormTreeData(item);
let ouputText = '';
if(this.searchModel === 'depHighlight') {
if(parents && parents.length) {
ouputText = parents.map(item => item[this.labelKey]).join(' > ');
ouputText = ouputText + ' > ' + item[this.labelKey];
} else {
ouputText = item[this.labelKey];
}
}
const bool1 = this.expandedKeys.indexOf(item[this.valueKey]) !== -1;
const len = parentId.length;
const bool2 = len > 0 ? this.expandedKeys.indexOf(parentId[len - 1]) !== -1 : bool1;
this.treeList.push({
id: item[this.valueKey],
name: item[this.labelKey],
source: item,
parentId, // 父级id数组
parents, // 父级id数组
rank, // 层级
showChild: bool1, //子级是否显示
open: bool1, //是否打开
show: (bool1 || bool2) || rank === 0, // 自身是否显示
hideArr: [],
ouputText,
orChecked: item.checked ? item.checked : false,
checked: item.checked ? item.checked : false,
halfChecked,
disabled: this.disabledKey && item[this.disabledKey] === true,
status: '',
operateCallback: (typeof item.operateCallback) === 'function' ? item.operateCallback : undefined
})
if(bool1) {
this.nodeInitContrl[item[this.valueKey]] = true;
} else {
this.nodeInitContrl[item[this.valueKey]] = undefined;
}
if (
(Array.isArray(item[this.childrenKey]) && item[this.childrenKey].length > 0) ||
(this.loadData && Array.isArray(item[this.childrenKey]) &&item[this.childrenKey].length === 0)
) {
let parentid = [...parentId],
parentArr = [...parents],
childrenid = [];
delete parentArr.children
parentid.push(item[this.valueKey]);
parentArr.push({
[this.valueKey]: item[this.valueKey],
[this.labelKey]: item[this.labelKey],
})
this._renderTreeList(item[this.childrenKey], rank + 1, parentid, parentArr);
} else if(item[this.childrenKey] === null) {
this.treeList[this.treeList.length - 1].lastRank = false;
} else {
this.treeList[this.treeList.length - 1].lastRank = true;
}
})
},
// 处理默认选择
_defaultSelect() {
this.treeList.forEach((v, i) => {
if (v.checked) {
this.treeList.forEach((v2, i2) => {
if (v.parentId.toString().indexOf(v2.parentId.toString()) >= 0) {
v2.show = true
if (v.parentId.includes(v2.id)) {
v2.showChild = true;
v2.open = true;
}
}
})
}
})
},
// 点击
async _treeItemTap(item, _index) {
const index = this.treeList.findIndex(it =>it.id === item.id);
if (item.lastRank === true) {
if (item.disabled === true) return
if (['checkbox', 'radio'].indexOf(this.funcMode) === -1) return
//点击最后一级时触发事件
this.treeList[index].checked = !this.treeList[index].checked;
if(this.changeVerify && (typeof this.changeVerify === 'function')) {
const current = Object.assign({}, item.source);
current.checked = item.checked;
const tip = this.changeVerify(current, this.multiple ? this._getCheckedParams() : [current]);
if(tip) {
this.treeList[index].checked = !this.treeList[index].checked;
uni.showToast({
title: tip,
icon: 'none'
});
return
}
};
this.treeList[index].halfChecked = false;
if(this.multiple && !this.checkStrictly && this.showHalfCheckedTips) {
this.updateHalfChecked(index);
} else if(this.multiple && this.checkStrictly) {
this.updateParentChecked(index);
}
this._fixMultiple(index);
this.$emit("change", this._getCheckedParams());
return;
} else if(this.ifSearch && this.keywords) {
// 搜索模式下不处理展开收起逻辑
return
}
// loadData实现
const isLoadData = this.loadData && !this.nodeInitContrl[item.id];
if(isLoadData) {
uni && uni.showLoading({ title: "请稍后..." });
const newChild = await this.loadData({$type: 'nodeLoad', source: this.treeList[index].source});
// 为了保证treeData数据的完整性异步加载的数据需要添加到treeData上
const treeItem = this.getItemFromTreeData(this.currentTreeData, item.id);
treeItem[this.childrenKey] = newChild && newChild.length ? newChild : undefined;
const parentId = item.parentId || [];
const lists = this.getRenderTreeList(newChild || [], item.rank + 1, parentId.concat([item.id]), [{[this.valueKey]: item[this.valueKey],[this.labelKey]: item[this.labelKey]}]);
this.nodeInitContrl[item.id] = true;
this.treeList.splice(index+1, 0, ...lists);
}
const childLen = this.treeList.filter(it => it.parentId.includes(item.id)).length;
if(!isLoadData && childLen > 50) {
uni && uni.showLoading({ title: "请稍后..." });
}
let list = this.treeList;
let id = item.id;
item.showChild = !item.showChild;
item.open = item.showChild ? true : !item.open;
list.forEach((childItem, i) => {
if (item.showChild === false) {
//隐藏所有子级
if (!childItem.parentId.includes(id)) {
return;
} else {
if (!this.foldAll) {
if (childItem.lastRank !== true && !childItem.open) {
childItem.showChild = false;
}
// 为隐藏的内容添加一个标记
if (childItem.show) {
childItem.hideArr[item.rank] = id
}
} else {
if (childItem.lastRank !== true) {
childItem.showChild = false;
}
}
childItem.show = false;
}
} else {
// 打开子集
if (childItem.parentId[childItem.parentId.length - 1] === id) {
childItem.show = true;
}
// 打开被隐藏的子集
if (childItem.parentId.includes(id) && !this.foldAll) {
if (childItem.hideArr[item.rank] === id) {
childItem.show = true;
if (childItem.open && childItem.showChild) {
childItem.showChild = true
} else {
childItem.showChild = false
}
childItem.hideArr[item.rank] = null
}
} else if(this.expandedMode === 'singe' && !childItem.parentId.includes(id)) {
if (childItem.id !== id) {
const bool1 = item.parentId.some(id => {
return (childItem.parentId && childItem.parentId.indexOf(id) !== -1) || childItem.rank === 0;
});
const childItemParentId = [...childItem.parentId];
const _id = childItemParentId ? childItemParentId.pop() : '';
const nodeList = [item, { parentId: childItemParentId.length ? childItemParentId : [] }];
if(!childItem.lastRank) {
if(item.parentId.indexOf(childItem.id) === -1) {
childItem.showChild = false;
}
if(!bool1 && !this.isSiblingNode([item, childItem])) {
childItem.show = false;
} else if(childItem.rank !== 0 && _id !== id && this.isSiblingNode(nodeList)) {
childItem.show = false;
} else if (childItem.rank !== 0 && _id !== id && !this.isSiblingNode(nodeList) && this.isParentSiblingNode(nodeList)) {
childItem.show = false;
}
} else {
if(!this.isSiblingNode([item, childItem]) && !bool1) {
childItem.show = false;
} else if(_id !== id && this.isSiblingNode(nodeList)) {
childItem.show = false;
} else if (_id !== id && !this.isSiblingNode(nodeList) && this.isParentSiblingNode(nodeList)) {
childItem.show = false;
}
}
}
}
}
});
setTimeout(() => {
uni && uni.hideLoading()
})
},
isSiblingNode(targetArr = []) {
const target1 = targetArr && targetArr.length ? targetArr[0] : {};
let target1Id = '';
if(target1.parentId && target1.parentId.length) {
target1Id = target1.parentId[target1.parentId.length - 1]
}
return targetArr.every(item => {
if(item && item.parentId && item.parentId.length) {
return target1Id === item.parentId[item.parentId.length - 1]
} else {
return target1Id === ''
}
})
},
isParentSiblingNode(targetArr = []) {
const target1 = targetArr && targetArr.length ? targetArr[0] : {};
let target1Id = '';
if(target1.parentId && target1.parentId.length) {
target1Id = target1.parentId[target1.parentId.length - 1]
}
return targetArr.every(item => {
if(item && item.parentId && item.parentId.length) {
return item.parentId.some(id => id === target1Id) && target1Id !== '';
} else {
return target1Id === '';
}
})
},
getThemeNodes(text) {
const _text = (text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${text || ''})`, 'gi');
return text ? text.replace(regex, `<span style="color: ${this._themeColor}">$1</span>`) : '';
},
getNodes(ouputText) {
if(this.keywords && ouputText) {
const key = (this.keywords || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${key})`, 'gi');
return ouputText.replace(regex, `<span style="color: ${this._themeColor}">$1</span>`);
}
return ouputText
},
getHalfCheckedFormTreeData(item) {
if (this.checkStrictly) {
return false;
} else if (!this.showHalfCheckedTips) {
return false
} else {
if(item[this.childrenKey] && item[this.childrenKey].length) {
return item[this.childrenKey].some(it => {
if(it.checked === true) {
return true;
} else if(it[this.childrenKey] && it[this.childrenKey].length) {
return this.getHalfCheckedFormTreeData(it);
} else {
return false;
};
});
} else {
return false;
}
}
},
getItemFromTreeData(treeData, id) {
if(id) {
let item = null;
(treeData || []).some(it => {
if(it[this.valueKey] === id) {
item = it
return true
} else if(it[this.childrenKey] && it[this.childrenKey].length) {
item = this.getItemFromTreeData(it[this.childrenKey], id)
return !!item
} else {
return false
}
});
return item
};
return null
},
_treeItemSelect(item, _index) {
const index = this.treeList.findIndex(it =>it.id === item.id);
if (item.disabled === true) return
this.treeList[index].checked = !this.treeList[index].checked;
if(this.changeVerify && (typeof this.changeVerify === 'function')) {
const current = Object.assign({}, item.source);
current.checked = item.checked;
const tip = this.changeVerify(current, this.multiple ? this._getCheckedParams() : [current]);
if(tip) {
this.treeList[index].checked = !this.treeList[index].checked;
uni.showToast({
title: tip,
icon: 'none'
});
return
}
};
this.treeList[index].halfChecked = false;
if (this.multiple && this.checkStrictly) {
if(!item.lastRank) {
const source = item.source || {};
const children = source[this.childrenKey] || [];
const checkedKeyList = this.getChildrenKeys(children);
this.treeList.forEach((v, i) => {
if(checkedKeyList.indexOf(v.id) !== -1) {
if(this.checkStrictlyModel === 'weak') {
if(!this.treeList[i].disabled) {
this.treeList[i].checked = this.treeList[index].checked
}
} else if(this.checkStrictlyModel === 'strong') {
this.treeList[i].checked = this.treeList[index].checked
}
}
})
}
this.updateParentChecked(index)
} else if(this.multiple && !this.checkStrictly && this.showHalfCheckedTips) {
this.updateHalfChecked(index);
} else {
this._fixMultiple(index);
}
this.$emit("change", this._getCheckedParams());
},
updateParentChecked(index) {
const parentId = (this.treeList[index].parentId || []).concat([]).reverse();
if(parentId && parentId.length) {
parentId.map(id => {
const parentTreeDataItem = this.getItemFromTreeData(this.currentTreeData, id);
const childrenIds = (parentTreeDataItem[this.childrenKey] || []).map(item => item[this.valueKey]);
const bool = this.treeList
.filter(it => childrenIds.indexOf(it.id) !== -1)
.every(it => it.checked === true);
const _bool = this.treeList
.filter(it => childrenIds.indexOf(it.id) !== -1)
.every(it => it.checked === false);
const parentItem = this.treeList.find(it => it.id === id);
if(parentItem) {
if (this.checkStrictlyModel === 'weak') {
if(bool && !parentItem.disabled) {
parentItem.checked = true;
} else if(_bool && !parentItem.disabled) {
parentItem.checked = false;
}
} else if(this.checkStrictlyModel === 'strong') {
if(bool) {
parentItem.checked = true;
} else {
parentItem.checked = false;
}
}
}
})
}
},
updateHalfChecked(index) {
const _parentId = this.treeList[index].parentId || [];
const parentId = _parentId.concat([]).reverse();
if(parentId && parentId.length) {
parentId.map(id => {
const parentTreeDataItem = this.getItemFromTreeData(this.currentTreeData, id);
const childrenIds = (parentTreeDataItem[this.childrenKey] || []).map(item => item[this.valueKey]);
const bool = this.treeList
.filter(it => childrenIds.indexOf(it.id) !== -1)
.every(it => it.checked === false && it.halfChecked === false);
const _bool = this.treeList
.filter(it => childrenIds.indexOf(it.id) !== -1)
.some(it => it.checked === true || it.halfChecked === true);
const parentItem = this.treeList.find(it => it.id === id);
if(parentItem) {
if(!parentItem.checked) {
if(bool) {
parentItem.halfChecked = false
} else if (_bool) {
parentItem.halfChecked = true
} else {
parentItem.halfChecked = false
}
}
}
})
}
if(this.treeList[index].checked == false) {
const source = this.treeList[index].source || {};
const children = source[this.childrenKey] || [];
const checkedKeyList = this.getChildrenKeys(children);
const bool = this.treeList.filter(item => checkedKeyList.indexOf(item.id) !== -1).some(item => item.checked);
if(bool) {
this.treeList[index].halfChecked = true;
}
}
},
showHalfChecked(item) {
if(this.multiple && !this.checkStrictly && item.halfChecked === true) {
return true
} else {
return false
}
},
getChildrenKeys(children) {
let keys = [];
(children || []).map(item => {
keys.push(item[this.valueKey])
if (item[this.childrenKey] && item[this.childrenKey].length) {
keys = keys.concat(this.getChildrenKeys(item[this.childrenKey]))
}
})
return keys
},
// 处理单选多选
_fixMultiple(index) {
if (!this.multiple) {
// 如果是单选
this.treeList.forEach((v, i) => {
if (i != index) {
this.treeList[i].checked = false
} else {
this.treeList[i].checked = true
}
})
}
},
// 重置数据
_reTreeList() {
this.treeList.forEach((v, i) => {
this.treeList[i].checked = v.orChecked
})
},
_initTree(){
this.treeList.length = 0;
if(this.loadData) {
this.currentTreeData = JSON.parse(JSON.stringify(this.treeData));
} else {
this.currentTreeData = this.treeData;
}
this._renderTreeList(this.currentTreeData);
this.$nextTick(() => {
this._defaultSelect();
})
},
_clear() {
this.treeList.map(item => {
if(this.multiple && this.checkStrictly) {
if(this.checkStrictlyModel === 'strong') {
item.checked = false;
} else if(this.checkStrictlyModel === 'weak') {
if(!item.disabled) {
item.checked = false;
};
} else {
item.checked = false;
}
} else {
if(!item.disabled) {
item.checked = false;
}
};
item.halfChecked = false;
})
this.$emit("change", this._getCheckedParams());
this.$emit("clear");
},
async onSearch(val) {
if(this.searchModel === 'remote') {
if(this.loadData) {
if(val) {
const newChild = await this.loadData({$type: 'remoteSearch', source: val});
const lists = this.getRenderTreeList(newChild);
this.treeList = [...lists];
} else {
this._initTree();
}
} else {
uni.showToast({
title: "使用远程搜索模式需要配置loadData函数",
icon: 'none'
})
}
} else {
this.keywords = val;
}
},
initUiModePopup(watched) {
uni.getSystemInfo({
success: (res) => {
this.top = (res.windowHeight - this.height) + 'px';
}
});
watched && watched();
watched = this.$watch(() => this.showTree, (bool) => {
if(bool) {
this._initTree();
} else {
this.$nextTick(() => {
setTimeout(() => {
this.treeList.length = 0;
this.nodeInitContrl = {};
})
})
}
}, {immediate: true});
},
initUiModePage(watched) {
this.top = '0px';
watched && watched();
watched = this.$watch(() => this.treeData, () => {
this.treeList.length = 0;
this.nodeInitContrl = {};
this.$nextTick(() => {
this._initTree();
});
}, {immediate: true, deep: true})
this.$nextTick(() => {
this.showTree = true;
});
},
uiModeInit() {
let watched = null;
if(this.uiMode === 'popup') {
this.initUiModePopup(watched);
} else if (this.uiMode === 'page') {
this.initUiModePage(watched);
} else {
this.initUiModePopup(watched);
}
}
},
mounted() {
console.log('----------next-tree组件完成挂载demo------------')
this.uiModeInit();
}
}
</script>
<style scoped>
@import "./style.css";
</style>