UNPKG

shu-c-view

Version:

rollup 打包vue@2.7组件库框架

1,638 lines (1,636 loc) 57.7 kB
/** * @desc tree 树状组件 */ import _assign from 'lodash/assign'; import _get from 'lodash/get'; import _set from 'lodash/set'; import _isEqual from 'lodash/isEqual'; 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() { 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)); } } 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' }, 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', '') ] ) ] ); // 二次确认框 } vNodes.push( this.$createElement( 'el-dropdown-item', { props: _omit(option, ['text', 'listeners', 'render']), nativeOn: { click: () => { if (_get(option, 'isClose', true)) { const elDropdownMenu = this.$refs[ `${node.data[this.nodeKey]}-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 } }, [ 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')) ) { scopedSlotVNode = { default: ({ node, data }) => { if (data.check) { this.checkedNodes.push(_get(data, this.curNodeKey)); } return this.createHandleMenu({ node, data }); } }; } else if (_isEmpty(this.$scopedSlots) && !_isNil(this.handleMenu)) { scopedSlotVNode = { default: ({ node, data }) => { if (data.check) { this.checkedNodes.push(_get(data, this.curNodeKey)); } return this.createHandleMenu({ node, data }); } }; } else if (_isEmpty(this.$scopedSlots) && _isNil(this.handleMenu)) { scopedSlotVNode = { default: ({ data }) => { if (data.check) { this.checkedNodes.push(_get(data, this.curNodeKey)); } return h('span', {}, [_get(data, this.defaultProps.label, '')]); } }; } const oLoadAction = this.lazy ? { load: this.loadNode } : { data: this.curData }; // 数据加载的方式 if (this.lazy && !this.isReloadTree) { oLoadAction.load = new Promise(resolve => { resolve([]); }); } _assign(this.defaultProps, { label: this.defaultProps.label }); let equalDraggableProps = {}; const equalDraggableOn = {}; if (this.isEqualDraggable) { equalDraggableProps = { draggable