shu-c-view
Version:
rollup 打包vue组件库框架
1,634 lines (1,631 loc) • 59.2 kB
JavaScript
/**
* @desc tree 树状组件
*/
import _assign from 'lodash/assign';
import _get from 'lodash/get';
import _set from 'lodash/set';
import _isEqual from 'lodash/isEqual';
import _isFunction from 'lodash/isFunction';
import _omit from 'lodash/omit';
import _has from 'lodash/has';
import _isEmpty from 'lodash/isEmpty';
import _isNil from 'lodash/isNil';
import _map from 'lodash/map';
import _find from 'lodash/find';
import _keys from 'lodash/keys';
import _hasIn from 'lodash/hasIn';
import _forEach from 'lodash/forEach';
import _join from 'lodash/join';
import _reverse from 'lodash/reverse';
import _includes from 'lodash/includes';
import _uniq from 'lodash/uniq';
import _concat from 'lodash/concat';
import _isArray from 'lodash/isArray';
import _filter from 'lodash/filter';
import _isObject from 'lodash/isObject';
import _pick from 'lodash/pick';
import { devConsole, apply } from '../helper/util.js';
import { getTreeMap, getNodePath } from '../helper/tree.js';
const BaseTree = {
name: 'BaseTree',
inheritAttrs: false,
/* model: {
prop: 'treeValue',
event: 'treeChange'
}, */
props: {
props: {
type: Object,
default() {
return {};
}
},
// 显示字段
// displayField: {
// type: String,
// default: 'name'
// },
// 值字段
// valueField: {
// type: String,
// default: 'id'
// },
// 默认树组件懒加载
lazy: {
type: Boolean,
default: false
},
api: {
type: String,
required: true
},
queryParams: {
type: Object,
default() {
return {};
}
},
// 根节点label
rootLabel: {
type: String,
default: '根目录'
},
// 根节点信息
/* root: {
type: Object,
default() {
return {
[this.defaultProps.value]: 0,
[this.defaultProps.label]: this.rootLabel,
[this.defaultProps.children]: []
};
}
}, */
// 是否渲染根节点
isRenderRoot: {
type: Boolean,
default: true
},
// 推荐 id 作为唯一键
nodeKey: {
type: String,
default: 'id'
},
// 过滤返回数据(该函数带一个参数'data'用来指向源数据)
loadFilter: {
type: Function
},
// 自定义内联 style
ctStyle: {
type: Object,
default() {
return {};
}
},
// 自定义样式 class
ctCls: {
type: Object,
default() {
return {};
}
},
// v-if
isRender: {
type: Boolean,
default: true
},
// v-show
isDisplay: {
type: Boolean,
default: true
},
// 事件
listeners: {
type: Object,
default: () => {}
},
// 定义外部 v-model 值,默认值 null 因为单选传入 String ,多选 Array 并不确定
/* treeValue: {
default() {
return null;
}
}, */
// 是否自动展开第一层树的节点 相当于设置 :default-expanded-keys="[0]"
// 如果设置了 default-expanded-keys 同时 isExpandFirstPath 为 true 的话会在 default-expanded-keys 中自动添加 0
isExpandFirstPath: {
type: Boolean,
default: true
},
// 控件右侧操作菜单栏(注意:如果配置了 handleMenuScope 作用域插槽,那么就算传入了 `handleMenu` 也不会进行生成)
handleMenu: {
type: Array
},
// 控件右侧操作菜单栏 icon
handleIcon: {
type: String,
default: 'el-icon-more'
},
// 数据源用于快速创建一个简易tree
store: {
type: Array,
default() {
return [
/* {
label: '一级 1',
children: [{
label: '二级 1-1',
check: true // 默认选中当前节点
}, {
label: '二级 1-2'
}]
} */
];
}
},
// 第一次载入时是否自动刷新 tree 数据
isReloadTree: {
type: Boolean,
default: true
},
// 是否只选取最里层的节点
isSelectedLastNode: {
type: Boolean,
default: false
},
// 选取的层级(不能和 isSelectedLastNode 同时设置)
selectedLevel: {
type: Number,
validator(value) {
return value >= 0;
}
},
// 需要禁用的节点,需要对象 nodeKey 的配置一般是id这样的唯一值,返回数据节点中disabled=false则不起效
disabledNodes: {
type: Array,
default() {
return [];
}
},
// 父子级联如果是false的情况下,是否需要父子联动(选中一个子节点级联选中对应的所有父级节点,取消某个父节点级联取消该节点下的所有子节点)
// 需要同时设置 :check-strictly: true 表示父子不相关联
checkStrictlyFalseCancelChildChecked: {
type: Boolean,
default: false
},
// 是否平级拖拽-节点只能在相同层级的level中拖拽
isEqualDraggable: {
type: Boolean,
default: false
}
},
computed: {
defaultExpandedKeys() {
return _get(this.$attrs, 'default-expanded-keys', []);
}
},
watch: {
// treeValue(val, oldVal) {
// if (!_isNil(val) && val.length === 0) {
// this.clearChecked();
// }
// /* if (!_isNil(val) && !_isEqual(val, oldVal)) {
// this.setCheckedKeys(val);
// } */
// }
// 监听要展开的节点
defaultExpandedKeys(val, oldVal) {
if (!_isEmpty(this.curData) && !_isEqual(val, oldVal)) {
this.$nextTick(this.setNodesExpanded);
}
},
curData() {
if (this.$attrs['show-checkbox']) {
// 多选
this.$nextTick(() => {
const treeRef = this.$refs['el-tree-ref'];
const data = treeRef.data;
if (this.isRenderRoot) {
const children = _get(data[0], this.defaultProps.children, []);
if (!_isEmpty(children)) {
treeRef
.getNode(this.root[this.defaultProps.value])
.expand(() => {}); // 展开根节点
this.setNodesExpanded();
}
} else {
this.setNodesExpanded();
}
});
} else {
this.$nextTick(this.setNodesExpanded);
}
}
},
data() {
this.handleMenuEnum = {
add: 'add', // 新增模式
edit: 'edit', // 编辑模式
delete: 'delete' // 删除模式
};
this.events = {
afterLoadStore: 'afterLoadStore' // 数据加载完成之后
};
this.curQueryParams = {};
this.editingNode = null; // 当前处于编辑状态的节点
this.isFirst = true; // 是否第一次加载,主要用于判断 `懒加载` 时是不是第一次请求
this.rootData = {}; // 根节点
this.checkedNodes = [];
this.root = {};
this.defaultProps = {
children: 'children',
label: 'label',
value: 'id',
isLeaf: 'leaf'
};
return {
curDefaultExpandedKeys: [],
// defaultExpandedKeys: _get(this.$attrs, 'default-expanded-keys', []),
editingValue: '', // 正在编辑的文本域值
curData: [], // 数据集
curNodeKey: this.nodeKey
};
},
created() {
if (!_isNil(this.props)) {
apply(this.defaultProps, this.props);
}
this.root = {
[this.defaultProps.value]: 0,
[this.defaultProps.label]: this.rootLabel,
[this.defaultProps.children]: []
};
if (this.curNodeKey !== this.defaultProps.value) {
this.curNodeKey = this.defaultProps.value;
}
this.rootData = { [this.curNodeKey]: this.root[this.curNodeKey] }; // 根节点 { [this.nodeKey]: 0 }
},
mounted() {
if (!this.lazy && this.isReloadTree) {
this.handLoadStore();
}
},
methods: {
/**
* @desc 手动触发查询
*/
handLoadStore() {
if (!this.lazy) {
// 非懒加载
this.loadStore()
.then(data => {
if (!_isEmpty(data)) {
if (this.isRenderRoot) {
this.root[this.defaultProps.children] = data;
this.curData = [this.root];
} else {
if (data.length > 0) {
this.rootData[this.curNodeKey] = data[0][this.curNodeKey];
}
this.curData = data;
}
} else {
this.curData = [];
}
// 数据读取完成触发事件
this.$emit(this.events.afterLoadStore, data);
this.$nextTick(() => {
// 设置需要禁用的节点
this.setDisabledNodes(this.disabledNodes);
});
// 复选和默认全部展开
if (
this.$attrs['show-checkbox'] &&
this.$attrs['default-expand-all']
) {
// 多选情况下默认勾选的节点 Array
const checkedKeys = _get(this.$attrs, 'default-checked-keys', []);
if (!_isEmpty(checkedKeys)) {
this.checkedNodes = _concat(this.checkedNodes, checkedKeys);
}
if (this.checkedNodes.length > 0) {
this.getTree().setCheckedKeys(_uniq(this.checkedNodes));
}
} else if (
this.$attrs['show-checkbox'] &&
!this.$attrs['default-expand-all']
) {
const treeNodesList = getTreeMap(
data,
this.defaultProps.children
);
const checkList = _map(
_filter(treeNodesList, v => v.check),
v => _get(v, this.curNodeKey)
);
if (!_isEmpty(checkList)) {
_set(
this.$attrs,
'default-checked-keys',
_uniq(
_concat(
checkList,
_get(this.$attrs, 'default-checked-keys', [])
)
)
);
}
}
// 自动展开第一行
if (
this.isExpandFirstPath &&
!_has(this.$attrs, 'default-expanded-keys') &&
!this.lazy
) {
this.curDefaultExpandedKeys.push(
this.isRenderRoot
? this.root[this.defaultProps.value]
: _get(data[0], this.curNodeKey)
);
}
return true;
})
.catch(error => {
throw new Error(error);
});
} else {
// 懒加载
}
},
/**
* @typedef {Object} options - 选项配置对象
* @property {Number} id - 指定在延迟开始前调用
* @property {String} text - 节点文本
* @property {String} label - 节点名称
* @property {Boolean} isLeaf - 是否子节点
* @property {Object} data - 后端提供的节点源数据对象
*/
/**
* @desc 加载子树数据的方法,仅当 lazy 属性为true 时生效
* @param {options} node - 节点信息
* @param {Promise} resolve - promise.resolve
* @method
*/
loadNode(node, resolve) {
if (node.level === 0 && this.isRenderRoot) {
// 自动展开第一行
if (this.isExpandFirstPath) {
this.curDefaultExpandedKeys.push(this.root[this.defaultProps.value]);
}
return resolve([this.root]);
}
this.loadStore(node)
.then(data => {
// this.curData = data; // 懒加载的数据不一样,是分批获取的,需要添加到指定的 curData的 某个节点内的 children 属性中
resolve(data);
// 数据读取完成触发事件
this.$emit(this.events.afterLoadStore, data);
this.$nextTick(() => {
// 设置需要禁用的节点
this.setDisabledNodes(this.disabledNodes);
if (this.$attrs['show-checkbox'] && this.lazy) {
setTimeout(() => {
// 多选情况下默认勾选的节点 Array
const checkedKeys = _get(
this.$attrs,
'default-checked-keys',
[]
);
if (!_isEmpty(checkedKeys)) {
this.checkedNodes = _concat(this.checkedNodes, checkedKeys);
}
if (this.checkedNodes.length > 0) {
this.getTree().setCheckedKeys(_uniq(this.checkedNodes));
}
}, 0);
}
});
return true;
})
.catch(() => resolve([]));
},
/**
* @desc 加载树
* @param {Object} node - 树的节点
* @method
*/
loadStore(node = {}) {
return new Promise(resolve => {
if (!_isNil(this.store) && !_isEmpty(this.store)) {
resolve(this.store);
return;
}
if (!this.api) {
resolve([]);
return;
}
const params = _assign(
{},
this.queryParams,
// { [this.curNodeKey]: _isEmpty(node) ? '' : _get(node.data, this.curNodeKey) },
_omit(this.curQueryParams, ['data', 'restful', 'headers'])
);
const nodeKeyValue = _get(node.data, this.curNodeKey);
if (!_isNil(nodeKeyValue)) {
params[this.curNodeKey] = nodeKeyValue;
}
let postData = {};
if (_has(this.curQueryParams, 'data')) {
if (_isObject(this.curQueryParams.data)) {
postData = this.curQueryParams.data;
} else {
params.data = this.curQueryParams.data;
}
}
let restfulData = {};
if (_has(this.curQueryParams, 'restful')) {
if (_isObject(this.curQueryParams.restful)) {
restfulData = this.curQueryParams.restful;
} else {
params.restful = this.curQueryParams.restful;
}
}
this.$api[this.api]({ params, data: postData, restful: restfulData })
.then(resList => {
if (this.loadFilter) {
resList = this.loadFilter(resList);
}
// const resData = [];
// const checkedNodes = [];
if (this.isFirst) {
this.isFirst = false;
if (resList.data.length > 0) {
this.rootData[this.curNodeKey] =
resList.data[0][this.curNodeKey];
}
}
// for (let i = 0; i < resList.data.length; i++) {
// const element = resList.data[i];
// element[this.props.label] = element[this.displayField];
// element[this.props.value] = element[this.valueField];
// 设置需要默认选中的节点
/* if (this.lazy) {
if (_has(element, 'check') && element.check) {
const node = _set({}, this.curNodeKey, element[this.curNodeKey]);
checkedNodes.push(node);
}
} */
// resData.push(element);
// }
/* if (this.lazy && checkedNodes.length > 0) {
setTimeout(() => {
// 默认勾选的节点 Array
this.getTree().setCheckedNodes(checkedNodes);
}, 0);
} */
// resolve(resData);
resolve(resList.data);
return true;
})
.catch(error => {
throw new Error(error);
});
});
},
/**
* @desc 设置查询参数
* @param {Object} params
* @method
*/
setQueryParams(params = {}) {
this.curQueryParams = {};
return Object.assign(this.curQueryParams, params);
},
/**
* @desc 获取根节点
*/
getRootData() {
return this.getTree().getNode(this.rootData[this.curNodeKey]);
},
/**
* @desc 获取 el-tree 对象
* @method
*/
getTree() {
return this.$refs['el-tree-ref'];
},
/**
* @desc 获取 tree 的所有选中节点
*/
getCheckedKeys() {
return this.getTree().getCheckedKeys();
},
/**
* @desc 刷新整棵树,不管节点
*/
refreshAll() {
// 增加$nextTick钩子防止在将静态数据store赋值为[]数组时未加$nextTick而无法清空树的数据,以为store=[]后直接调用refreshAll()方法会比props中store数据清除逻辑在之前执行导致树无法清除数据
this.$nextTick(() => {
this.loadStore()
.then(data => {
if (!_isEmpty(data)) {
if (this.isRenderRoot) {
_set(this.root, this.defaultProps.children, data);
// this.root.children = data;
this.curData = [this.root];
} else {
this.curData = data;
}
// 自动展开第一行
if (
this.isExpandFirstPath &&
!_has(this.$attrs, 'default-expanded-keys') &&
!this.isRenderRoot
) {
this.curDefaultExpandedKeys.push(
_get(data[0], this.curNodeKey)
);
}
// 多选情况下默认勾选的节点 Array
const checkedKeys = _get(this.$attrs, 'default-checked-keys', []);
if (checkedKeys && checkedKeys.length > 0) {
this.getTree().setCheckedKeys(checkedKeys);
}
} else {
this.curData = [];
}
// 数据读取完成触发事件
this.$emit(this.events.afterLoadStore, data);
return true;
})
.catch(error => {
throw new Error(error);
});
});
},
/**
* @desc 刷新某个节点
* @param {*} nodeData
* @method
* @private
*/
// eslint-disable-next-line no-empty-function
refreshNode() {},
/**
* @desc 刷新整棵树
* @method
*
*/
refresh() {
const node = this.getTree().getNode(this.rootData[this.curNodeKey]);
node.loading = true;
this.loadStore(node)
.then(resData => {
this.getTree().updateKeyChildren(node.data[this.curNodeKey], resData);
return true;
})
.catch(error => {
throw new Error(error);
})
.finally(() => {
node.loading = false;
});
},
/**
* @desc 设置节点禁用状态
*/
setDisabledNodes(disabledNodes) {
// 设置需要禁用的节点
if (!_isNil(this.getTree())) {
if (!_isNil(disabledNodes) && !_isEmpty(disabledNodes)) {
for (let i = 0, len = disabledNodes.length; i < len; i++) {
const node = this.getTree().getNode(disabledNodes[i]);
if (!_isNil(node)) {
const disabled = _get(node, 'data.disabled'); // 禁用状态
if (_isNil(disabled) && disabled !== false) {
// node.data.disabled = true;
this.$set(node.data, 'disabled', true);
}
}
}
}
}
},
/**
* @desc 更新某个节点(支持懒加载和非懒加载)
* @example
* this.$refs['complicate-tree-ref'].updateNode(node, { label: value });
*/
updateNode(node, params = {}) {
if (!_isNil(params) && _hasIn(node.data, _keys(params))) {
const key = _keys(params)[0];
_set(node, `data.${key}`, params[key]);
}
},
/**
* @desc 删除某个节点
* @param {Object} node - 当前选中节点
*/
deleteNode(node = {}) {
if (!_isEmpty(node)) {
const parent = node.parent;
const children = parent.data[this.defaultProps.children] || parent.data;
const index = children.findIndex(d => d.id === node.data.id);
children.splice(index, 1);
}
},
/**
* @desc 默认展开指定的节点
*/
expandedNode(node = {}) {
// node.expanded = true;
/* if (!_isEmpty(node) && _has(node, this.valueField)) {
this.$refs['el-tree-ref'].store.nodesMap[
node.data[this.valueField]
].expanded = true; // 默认展开指定的1个节点,如果是3层节点,第3层设置展开那么上面的2层还是没有展开看起来还是关闭的样子
} */
if (
!_isEmpty(node) &&
!_isNil(node.data) &&
_has(node, this.curNodeKey)
) {
const whileFn = parentNode => {
if (!parentNode.expanded) {
parentNode.expanded = true;
}
if (!_isNil(parentNode.parent)) {
whileFn(parentNode.parent);
}
};
if (_has(node, 'expanded')) {
const curNode = this.$refs['el-tree-ref'].store.nodesMap[
node.data[this.curNodeKey]
];
if (!_isNil(curNode) && !_isNil(curNode.parent)) {
whileFn(curNode.parent);
}
if (!curNode.expanded) {
curNode.expanded = true; // 默认展开指定节点
}
}
}
},
/**
* @desc 新增加一个节点
*/
insertNode(node, data = {}) {
if (!_isEmpty(node) && _has(data, this.curNodeKey)) {
if (_isNil(_get(node.data, this.defaultProps.children))) {
this.$set(node.data, this.defaultProps.children, []);
}
node.data[this.defaultProps.children].push(data);
setTimeout(() => {
this.expandedNode(node); // 默认展开指定节点
}, 0);
}
},
/**
* @desc 获取节点的所有上层节点
* @param {Object} node - 当前 tree 的节点对象
* @param {Boolean} isHaveSelf - 是否包含自身的值
*/
getNodeParentLevel(node, isHaveSelf = true) {
if (
!_has(node, 'data') &&
!_has(node.data, this.curNodeKey) &&
!_has(node.data, this.curNodeKey)
) {
return;
}
const levelStr = isHaveSelf
? [_get(node.data, this.defaultProps.label)]
: [];
const whileFn = curNode => {
if (_has(curNode, `data.${this.defaultProps.label}`)) {
levelStr.push(_get(curNode.data, this.defaultProps.label));
}
if (_has(curNode, 'parent') && !_isNil(curNode.parent)) {
whileFn(curNode.parent);
}
};
if (_has(node, 'parent') && !_isNil(node.parent)) {
whileFn(node.parent);
} else {
levelStr.push(_get(node.data, this.defaultProps.label));
}
return _join(_reverse(levelStr), '/');
},
/**
* @desc 通过 keys 设置目前勾选的节点
* @method
* @example
* this.$refs['base-tree'].setCheckedKeys([2, 5])
*/
setCheckedKeys(keys = []) {
/* const defaultCheckNodes = this.getCheckedNodes();
if (!_isEmpty(defaultCheckNodes)) {
for (let i = 0; i < defaultCheckNodes.length; i++) {
keys.push(_get(defaultCheckNodes[i], this.curNodeKey));
}
} */
this.getTree().setCheckedKeys(keys);
},
/**
* @desc 若节点可被选择,则返回目前被选中的节点所组成的数组
* @method
* @return Array
*/
getCheckedNodes() {
return this.getTree().getCheckedNodes();
},
/**
* @desc 清空树的数据(静态数据 store 清空请在外部自行操作)
*/
clearData() {
if (this.lazy) {
if (this.isRenderRoot) {
this.getTree().remove(_get(this.root, this.curNodeKey, 0));
} else {
const nodesMap = this.getTree().store.nodesMap;
_forEach(nodesMap, value => {
const id = _get(value, `data.${this.curNodeKey}`);
if (!_isNil(id)) {
this.getTree().remove(id);
}
});
/* this.getTree().remove(1);
this.getTree().remove(2);
this.getTree().remove(3); */
}
} else {
this.curData = [];
}
if (
_has(this.$listeners, 'clearData') ||
_has(this.$listeners, 'clear-data')
) {
this.$emit('clearData');
}
},
/**
* @desc 清空选中的节点
* @method
*/
clearChecked() {
this.getTree().setCheckedKeys([]);
},
/**
* @desc 节点被点击时的回调
* @param {Object} record
* @param {*} node
* @param {*} tree
* @event
*/
nodeClick(record, node, tree) {
if (_has(this.listeners, 'nodeClick')) {
this.listeners.nodeClick(record, node, tree);
return;
}
const eventName = _has(this.$listeners, 'nodeClick')
? 'nodeClick'
: 'node-click';
this.$emit(eventName, record, node, tree);
},
/**
* @desc 节点选中状态发生变化时的回调
* @param {Object} record - 节点记录
* @param {Boolean} checked - 节点本身是否被选中
* @param {Array} childCheckNodes - 节点的子树中是否有被选中的节点
* @event
*/
checkChange(record, checked, childCheckNodes) {
if (_has(this.listeners, 'checkChange')) {
this.listeners.checkChange(record, checked, childCheckNodes);
return;
}
const eventName = _has(this.$listeners, 'checkChange')
? 'checkChange'
: 'check-change';
this.$emit(eventName, record, checked, childCheckNodes);
// 触发v-model input事件
/* const treeEventName = _has(this.$listeners, 'treeChange')
? 'treeChange'
: 'tree-change';
this.$emit(
treeEventName,
_map(this.getTree().getCheckedNodes(), o => _get(o, this.valueField))
); */
if (
this.checkStrictlyFalseCancelChildChecked &&
(_has(this.$attrs, 'check-strictly') || this.$attrs['check-strictly'])
) {
if (checked) {
this.setCheckedParentTreeNodes(record);
} else {
this.setCancelChildTreeNodes(record);
}
}
},
/**
* @desc 勾选该节点以上的所有未选中父级节点
*/
setCheckedParentTreeNodes(record) {
const treeNode = this.getTree().getNode(record[this.curNodeKey]);
const parentNodes = this.getNodeParentNodes(treeNode);
for (let i = 0, len = parentNodes.length; i < len; i++) {
const parentNode = parentNodes[i];
const parentNodeData = _get(parentNode, 'data', {});
if (!parentNode.checked) {
this.$nextTick(() => {
this.getTree().setChecked(
_get(parentNodeData, this.curNodeKey, ''),
true
);
});
}
}
},
/**
* @desc 取消勾选该节点以下的所有选中子级节点
*/
setCancelChildTreeNodes(record) {
const treeNode = this.getTree().getNode(record[this.curNodeKey]);
const childCheckedValues = this.getNodeCheckedChild(treeNode);
for (let i = 0, len = childCheckedValues.length; i < len; i++) {
const value = childCheckedValues[i];
const treeNode = this.getTree().getNode(value);
this.getTree().setChecked(
_get(treeNode, `data.${this.curNodeKey}`, ''),
false
);
}
},
/**
* @desc 当复选框被点击的时候触发
* @param {Object} node - 节点对象
* @param {Object} treeCheckedNode - 树目前的选中状态对象
* @event
*/
nodeBoxCheck(node, treeCheckedNode) {
if (_has(this.listeners, 'check')) {
this.listeners.check(node, treeCheckedNode);
return;
}
this.$emit('check', node, treeCheckedNode);
// 触发v-model input事件
this.$emit(
'treeChange',
_map(this.getTree().getCheckedNodes(), o =>
_get(o, this.defaultProps.value)
),
node,
treeCheckedNode
);
},
/**
* @desc 当前选中节点变化时触发的事件 点击节点,并不是复选框
* @param {Object} record - 当前节点的数据
* @param {Object} node - 当前节点的 Node 对象
* @event
*/
currentChange(record, node) {
if (_has(this.listeners, 'currentChange')) {
this.listeners.currentChange(node, record, node);
return;
}
const eventName = _has(this.$listeners, 'currentChange')
? 'currentChange'
: 'current-change';
this.$emit(eventName, record, node);
// 触发v-model input事件
/* const treeEventName = _has(this.$listeners, 'treeChange')
? 'treeChange'
: 'tree-change';
if (this.$attrs['show-checkbox']) {
this.$emit(
treeEventName,
_map(this.getTree().getCheckedNodes(), o => _get(o, this.valueField))
);
} else {
this.$emit(treeEventName, _get(record, this.valueField));
} */
},
/**
* @desc 当某一节点被鼠标右键点击时会触发该事件
* @param {*} event
* @param {*} nodeData
* @param {*} node
*/
nodeContextmenu(event, record, node, nodeElement) {
const eventName = _has(this.$listeners, 'nodeContextmenu')
? 'nodeContextmenu'
: 'node-contextmenu';
this.$emit(eventName, event, record, node, nodeElement);
event.preventDefault();
},
/**
* @desc 创建 el-dropdown-item
* @method
* @private
* @param {Object} data - 当前节点的数据
* @param {Object} node - 当前节点的 Node 对象
*/
createElDropdownItem({ node, data }) {
const vNodes = [];
if (!_isNil(this.handleMenu)) {
for (let i = 0, len = this.handleMenu.length; i < len; i++) {
const option = this.handleMenu[i];
let renderNode = option.text;
if (!_has(option, 'divided')) {
option.divided = true;
}
if (!_has(option, 'isClose')) {
option.isClose = true; // 点击关闭下拉面板
}
if (_has(option, 'render')) {
renderNode = option.render(this.$createElement); // 自定义节点
}
if (_has(option, 'isPopconfirm') && option.isPopconfirm) {
renderNode = this.$createElement(
'el-popconfirm',
{
props: {
title: _get(option, 'title', ''),
placement: 'left',
iconColor: 'red',
..._get(option, 'props', {})
},
on: {
onConfirm: () => {
_has(option, 'listeners.onConfirm') &&
option.listeners.onConfirm({ node, data });
},
confirm: () => {
_has(option, 'listeners.onConfirm') &&
option.listeners.onConfirm({ node, data });
_has(option, 'listeners.confirm') &&
option.listeners.confirm({ node, data });
},
onCancel: () => {
_get(option, 'listeners.onCancel', () => {});
},
cancel: () => {
_get(option, 'listeners.onCancel', () => {});
_get(option, 'listeners.cancel', () => {});
}
}
},
[
this.$createElement(
'span',
{ slot: 'reference', style: 'display: block;' },
[
_has(option, 'render')
? option.render(this.$createElement)
: _get(option, 'text', '')
]
)
]
); // 二次确认框
}
const itemProps = _omit(option, [
'text',
'listeners',
'render',
'disabled',
'isRender'
]);
if (_has(option, 'disabled') && _isFunction(option.disabled)) {
itemProps.disabled = option.disabled(data);
}
let isRender = true;
if (_has(option, 'isRender') && _isFunction(option.isRender)) {
isRender = option.isRender(data);
if (!isRender) {
continue;
}
}
vNodes.push(
this.$createElement(
'el-dropdown-item',
{
props: itemProps,
nativeOn: {
click: () => {
if (_get(option, 'isClose', true)) {
const elDropdownMenu = this.$refs[
`${node.data[this.curNodeKey]}-tree-el-dropdown`
].$children[0];
this.hideHandleMenuPopconfirmEl(elDropdownMenu);
this.$refs[
`${node.data[this.curNodeKey]}-tree-el-dropdown`
].hide(); // 隐藏 Dropdown 下拉菜单
// document.body.click(); // 用于隐藏 isPopconfirm: true 时的气泡确认框
}
if (_has(option, 'listeners.click')) {
option.listeners.click({ node, data });
}
}
}
},
[renderNode]
)
);
}
}
return vNodes;
},
/**
* @desc 创建点击树形控件右侧更多按钮展示的下拉菜单
* @method
* @private
* @param {Object} data - 当前节点的数据
* @param {Object} node - 当前节点的 Node 对象
*/
createHandleMenu({ node, data }) {
const h = this.$createElement;
let textNode = h('span', { class: 'el-tree-node__label' }, [
h(
'div',
{
class: {
'base-tree-node-item': true,
[`item-${this._uid}-${node.data[this.curNodeKey]}`]: true
}
},
[h('span', {}, [node.label]), this.createEditNode(h, node)]
)
]);
let menuNode = _isNil(this.handleMenu)
? undefined
: h(
'el-dropdown-menu',
{ slot: 'dropdown' },
this.createElDropdownItem({ node, data })
);
if (_has(this.$scopedSlots, 'default')) {
// 默认插槽
textNode = h(
'div',
{
class: {
'base-tree-node-item': true,
[`item-${this._uid}-${node.data[this.curNodeKey]}`]: true
},
on: {
click: () => {
this.$refs['el-tree-ref'].setCurrentKey(
node.data[this.curNodeKey]
);
this.nodeClick(node.data, node);
}
}
},
[
this.$scopedSlots.default({ node, data }),
this.createEditNode(h, node)
]
);
}
if (_has(this.$scopedSlots, 'handleMenuScope')) {
// 下拉菜单插槽
menuNode = this.$scopedSlots.handleMenuScope({ node, data });
}
let handleIconNode = h('i', { class: this.handleIcon }, []);
if (_has(this.$scopedSlots, 'handleIconScope')) {
handleIconNode = this.$scopedSlots.handleIconScope({ node, data });
}
const vNode = h(
'span',
{
class: 'handle-menu-tree-node',
on: {
click: event => {
if (event.target.tagName === 'I') {
event.stopPropagation();
event.preventDefault();
return false; // i 图标标签
}
}
}
},
[
textNode,
!_isNil(menuNode)
? h('span', { class: { 'tree-el-dropdown-box': true } }, [
h(
'el-dropdown',
{
ref: `${node.data[this.curNodeKey]}-tree-el-dropdown`,
props: {
'hide-on-click': false,
trigger: 'click',
size: 'mini'
},
on: {
'visible-change': visible => {
if (
!_isNil(this.editingNode) &&
node.data[this.curNodeKey] !==
this.editingNode.data[this.curNodeKey]
) {
this.changeEditModel(false);
}
if (!visible) {
// 下拉框隐藏
const elDropdownMenu = this.$refs[
`${node.data[this.curNodeKey]}-tree-el-dropdown`
].$children[0];
this.hideHandleMenuPopconfirmEl(elDropdownMenu);
}
},
command: val => {
if (_isNil(val)) {
return;
}
// 编辑模式
if (val === this.handleMenuEnum.edit) {
this.editingValue =
node.data[this.defaultProps.label]; // 正在编辑的值
this.editingNode = node; // 正在编辑的节点
this.changeEditModel();
}
// 新增模式
if (val === this.handleMenuEnum.add) {
this.changeEditModel(false);
if (_has(this.$listeners, 'addNodeHandle')) {
this.$emit('addNodeHandle', node, node.data, val);
} else {
const newChild = {
id: `${this._uid}-${Math.ceil(
Math.random() * 1000
)}`,
label: '新增节点',
children: []
};
if (!node.data.children) {
this.$set(node.data, 'children', []);
}
node.data.children.push(newChild);
setTimeout(() => {
this.expandedNode(node); // 默认展开指定节点
}, 0);
}
}
// 删除模式
if (val === this.handleMenuEnum.delete) {
this.changeEditModel(false);
this.$emit('deleteNodeHandle', node, node.data, val);
}
if (
!_includes(
_map(this.handleMenuEnum, value => value),
val
)
) {
this.$emit(
'dropdownCommand',
val,
node.data,
this.$refs[
`${node.data[this.curNodeKey]}-tree-el-dropdown`
]
);
}
}
}
},
[
h('span', { class: 'el-dropdown-link' }, [handleIconNode]),
menuNode
]
)
])
: h('span', { class: 'el-dropdown-link' }, [handleIconNode])
]
);
return vNode;
},
/**
* @desc 创建编辑节点 el-input
*/
createEditNode(h, node) {
return h('div', { 'base-node-item_wrap': true }, [
h('el-input', {
class: 'base-tree-node-edit-input',
attrs: {
maxlength: '30'
},
props: {
value: this.editingValue,
'show-word-limit': true
}, // node.data[this.displayField]
on: {
input: val => {
this.editingValue = val;
}
},
nativeOn: {
click: event => {
event.stopPropagation();
event.preventDefault();
return false; // 静止事件冒泡,触发到 tree 的 node-click 事件
}
}
}),
h('i', {
attrs: { title: '提交' },
class: { 'el-icon-check': true, 'confirm-btn': true },
on: {
click: event => {
this.$emit('editNodeHandle', node, this.editingValue, event);
setTimeout(() => {
this.changeEditModel(false);
}, 200);
}
}
}),
h('i', {
attrs: { title: '取消' },
class: { 'el-icon-close': true, 'cancel-btn': true },
on: {
click: () => {
this.changeEditModel(false);
}
}
})
]);
},
/**
* @desc 切换只读和编辑模式
*/
changeEditModel(model = true) {
if (
!_isNil(this.editingNode) &&
!_isNil(
document.getElementsByClassName(
`item-${this._uid}-${this.editingNode.data[this.curNodeKey]}`
)[0]
)
) {
const aNodeList = document.getElementsByClassName(
`item-${this._uid}-${this.editingNode.data[this.curNodeKey]}`
)[0].childNodes;
/* const aNode = document.getElementsByClassName(
`item-${this._uid}-${this.editingNode.data[this.valueField]}`
)[0];
aNode.style.width = 'auto'; */
aNodeList[0].style.display = model ? 'none' : 'inline-block';
aNodeList[1].style.display = model ? 'inline-block' : 'none';
const offsetLeft = aNodeList[1].offsetLeft; // 距离左侧容器的间距
// eslint-disable-next-line no-unused-vars
const editBoxOffsetWidth = aNodeList[1].offsetWidth; // 编辑操作容器的宽度
// eslint-disable-next-line no-unused-vars
const treeBoxOffsetWidth = this.$refs['el-tree-ref'].$el.offsetWidth; // tree 面板容器的宽度
aNodeList[1].style.position = 'relative';
if (model && offsetLeft > 60) {
aNodeList[1].style.left = `-${offsetLeft / 2}px`;
}
if (!model) {
aNodeList[1].style.left = '0px';
}
if (this.$attrs['show-checkbox']) {
let checkBoxNode;
if (
!_isNil(this.handleMenu) &&
!_has(this.$scopedSlots, 'handleMenuScope')
) {
checkBoxNode = _get(
aNodeList[1],
'parentNode.parentNode.parentNode.children[0].parentNode.parentNode.childNodes'
);
}
if (_has(this.$scopedSlots, 'handleMenuScope')) {
checkBoxNode = _get(
aNodeList[1],
'parentNode.parentNode.parentNode.children'
);
if (!_has(this.$scopedSlots, 'default')) {
checkBoxNode = _get(
aNodeList[1],
'parentNode.parentNode.parentNode.parentNode.children'
);
}
}
if (model && !_isNil(checkBoxNode)) {
// 复选框
if (
!_isNil(checkBoxNode) &&
checkBoxNode.length > 0 &&
_includes(checkBoxNode[1].className, 'el-checkbox')
) {
checkBoxNode[1].style.display = 'none';
}
} else if (
!_isNil(checkBoxNode) &&
checkBoxNode.length > 0 &&
_includes(checkBoxNode[1].className, 'el-checkbox')
) {
checkBoxNode[1].style.display = 'inline';
}
}
// aNodeList[1].style.marginLeft = '-20px';
// console.info('aNodeList[1] ', aNodeList[1].offsetWidth, this.$refs['el-tree-ref'], treeBoxOffsetWidth, editBoxOffsetWidth, offsetLeft);
if (!model) {
this.editingValue = '';
this.editingNode = null;
}
}
},
/**
* @desc 获取指定树节点下所有子节点的 valueField 值
* @returns Array
*/
getNodeChildNodeKeys(treeNode) {
const valueFields = [];
const that = this;
const whileHandle = function(nodes) {
for (let i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
valueFields.push(node.data[that.curNodeKey]);
if (_has(node, 'childNodes') && !_isEmpty(node, 'childNodes')) {
whileHandle(node.childNodes);
}
}
};
if (_isArray(treeNode)) {
for (let n = 0, len = treeNode.length; n < len; n++) {
if (
_has(treeNode[n], 'childNodes') &&
!_isEmpty(treeNode[n], 'childNodes')
) {
for (let i = 0, len = treeNode[n].childNodes.length; i < len; i++) {
const childNode = treeNode[n].childNodes[i];
valueFields.push(childNode.data[this.curNodeKey]);
if (
_has(childNode, 'childNodes') &&
!_isEmpty(childNode, 'childNodes')
) {
whileHandle(childNode.childNodes);
}
}
}
}
} else if (
_has(treeNode, 'childNodes') &&
!_isEmpty(treeNode, 'childNodes')
) {
for (let i = 0, len = treeNode.childNodes.length; i < len; i++) {
const childNode = treeNode.childNodes[i];
valueFields.push(childNode.data[this.curNodeKey]);
if (
_has(treeNode, 'childNodes') &&
!_isEmpty(treeNode, 'childNodes')
) {
whileHandle(childNode);
}
}
}
return valueFields;
},
/**
* @desc 获取当前节点下的所有选中节点
*/
getNodeCheckedChild(treeNode) {
if (_has(treeNode, 'childNodes') && _isEmpty(treeNode, 'childNodes')) {
return [];
}
const valueFields = [];
const childNodes = treeNode.childNodes;
const that = this;
const whileHandle = function(nodes) {
for (let i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
if (node.checked) {
valueFields.push(node.data[that.curNodeKey]);
}
if (_has(node, 'childNodes') && !_isEmpty(node, 'childNodes')) {
whileHandle(node.childNodes);
}
}
};
for (let i = 0, len = childNodes.length; i < len; i++) {
const childNode = childNodes[i];
if (childNode.checked) {
valueFields.push(childNode.data[this.curNodeKey]);
}
if (
_has(childNode, 'childNodes') &&
!_isEmpty(childNode, 'childNodes')
) {
whileHandle(childNode.childNodes);
}
}
return valueFields;
},
/**
* @desc 获取当前节点以上的所有父级节点,根节点除外
*/
getNodeParentNodes(treeNode) {
const parentNodes = [];
if (treeNode.level === 0) {
return parentNodes;
}
if (
!_has(treeNode, 'data') &&
!_has(treeNode.data, this.curNodeKey) &&
!_has(treeNode.data, this.curNodeKey)
) {
return parentNodes;
}
const whileFn = curNode => {
if (curNode.level !== 0) {
parentNodes.push(curNode);
}
if (_has(curNode, 'parent') && !_isNil(curNode.parent)) {
whileFn(curNode.parent);
}
};
if (_has(treeNode, 'parent') && !_isNil(treeNode.parent)) {
whileFn(treeNode.parent);
}
return parentNodes;
},
// 获取配置选项
getDefaultProps() {
return this.defaultProps;
},
/**
* @desc 判断某个节点是否位于传入的某几个树节点内也就是在childNodes中
* @returns Boolean
*/
isTreeNodesIncludeNode(treeNodes, treeNode) {
const that = this;
let isInclude = false;
const whileHandle = nodes => {
for (let i = 0, len = nodes.length; i < len; i++) {
if (
treeNode.data[that.curNodeKey] === nodes[i].data[that.curNodeKey]
) {
isInclude = true;
break;
}
if (
_has(nodes[i], 'childNodes') &&
!_isEmpty(nodes[i], 'childNodes')
) {
whileHandle(nodes[i].childNodes);
}
}
};
for (let i = 0, len = treeNodes.length; i < len; i++) {
const checkedNode = treeNodes[i];
if (
checkedNode.data[this.curNodeKey] === treeNode.data[this.curNodeKey]
) {
continue;
}
if (
_has(checkedNode, 'childNodes') &&
!_isEmpty(checkedNode, 'childNodes')
) {
whileHandle(checkedNode.childNodes);
}
}
return isInclude;
},
// 设置节点展开
setNodesExpanded() {
const treeRef = this.$refs['el-tree-ref'];
const defaultExpandedKeys = _has(this.$attrs, 'default-expanded-keys')
? this.defaultExpandedKeys
: this.curDefaultExpandedKeys;
const expandedNodeKeys = [];
_forEach(defaultExpandedKeys, key => {
const key2Node = treeRef.getNode(key);
if (!_isNil(key2Node)) {
const { level } = key2Node;
if (
level > 1 ||
!this.isRenderRoot ||
(defaultExpandedKeys.length === 1 &&
defaultExpandedKeys[0] === this.root[this.defaultProps.value])
) {
const pathList = getNodePath({
tree: this.curData,
id: key,
nodeKey: this.curNodeKey,
children: this.defaultProps.children
});
expandedNodeKeys.push(...pathList);
}
}
});
_forEach(_uniq(expandedNodeKeys), key => {
treeRef.getNode(key).expand();
});
},
// 隐藏 handleMenu `控件右侧操作菜单栏` 的 Popconfirm 气泡确认框
hideHandleMenuPopconfirmEl(elDropdownMenu) {
const elDropdownItems = elDropdownMenu.$children;
if (!_isEmpty(elDropdownItems)) {
const popconfirm = _find(elDropdownItems, item => {
return (
_get(item, '$children[0].$options.name', '') === 'ElPopconfirm'
);
});
if (!_isNil(popconfirm)) {
popconfirm.$children[0].cancel();
}
}
},
allowDropHandle(moveNode, inNode, type) {
const { level: moveLevel } = moveNode;
const { level: inLevel } = inNode;
if (moveLevel !== inLevel) {
return false;
}
if (type === 'inner') {
return false;
}
return true;
},
nodeDropHandle(moveNode, insertNode) {
if (
_has(insertNode, 'parent.data') &&
_get(insertNode, 'parent.level', 0) !== 0
) {
const {
parent: { data }
} = insertNode;
const parentId = data[this.defaultProps.value];
const parentNode = this.getTree().getNode(parentId);
let childIds = [];
if (!_isNil(parentNode)) {
childIds = _map(
_get(parentNode.data, this.defaultProps.children, []),
this.defaultProps.value
);
} else {
// 顶级节点
childIds = _map(
_get(this.getTree().data[0], this.defaultProps.children, []),
this.defaultProps.value
);
}
this.$emit(
'equalNodeDrop',
childIds,
_pick(insertNode.parent.data, [this.props.value, this.props.label])
);
} else {
// 没有顶级父节点时当前level=0的节点在同一级拖拽
const {
parent: { data, level }
} = insertNode;
let childIds = [];
if (level === 0 && _isArray(data)) {
childIds = _map(data, this.defaultProps.value);
}
this.$emit('equalNodeDrop', childIds, {
[this.props.label]: '',
[this.props.value]: 0
});
}
}
},
render(h) {
// v-if
if (_isEqual(this.isRender, false)) {
return h();
}
const style = _assign({}, _get(this.$props, 'ctStyle', { width: '100%' }));
// v-show
if (_isEqual(this.isDisplay, false)) {
_set(style, 'display', 'none');
}
let scopedSlotVNode;
if (
!_isEmpty(this.$scopedSlots) &&
(_has(this.$scopedSlots, 'default') ||
_has(this.$scopedSlots, 'handleMenuScope') ||
_has(this.$scopedSlots, 'handleIconScope'))