<template> <div class="design-tree-container"> <context-menu-container> <el-tree v-if="inited" ref="treeexpbar_tree" class="design-tree" node-key="id" lazy :show-checkbox="!isSingleSelect" :check-on-click-node="!isSingleSelect" :default-expanded-keys="expandedKeys" :props="{ label: 'text', isLeaf: 'leaf', children: 'children' }" :load="load" :highlight-current="true" :expand-on-click-node="false" @check="onCheck" @current-change="selectionChange" :filter-node-method="filterNode" :empty-text="$t('entities.ibizorder.treeexp_treeview.nodata')" > <template slot-scope="{ node, data }"> <context-menu :ref='data.id' :isBlocked="true" :contextMenuStyle="{width: '100%'}" :data="node" :renderContent="renderContextMenu" @showContext="showContext(data,$event)"> <tooltip transfer style="width: 100%;" max-width="2000" placement="right"> <div class="tree-node" @dblclick="doDefaultAction(node)"> <span class="icon"> <i v-if=" data.iconcls && !Object.is(data.iconcls, '')" :class="data.iconcls"></i> <img v-else-if="data.icon && !Object.is(data.icon, '')" :src="data.icon" /> <icon v-else-if="isOutputIconDefault" type="ios-paper-outline"></icon> </span> <span class="text"> <span v-if="data.html" v-html="data.html"></span> <span v-else>{{ data.isUseLangRes ? $t(data.text) : data.text }}</span> </span> </div> <template slot="content"> <span v-if="data.html" v-html="data.html"></span> <span v-else>{{ data.isUseLangRes ? $t(data.text) : data.text }}</span> </template> </tooltip> </context-menu> </template> </el-tree> </context-menu-container> </div> </template> // 基于 @CONTROL/树视图/CONTROL-BASE.vue.ftl 生成 <script lang='tsx'> import { Vue, Component, Prop, Provide, Emit, Watch, Model,Inject } from 'vue-property-decorator'; import { CreateElement } from 'vue'; import { Subject, Subscription } from 'rxjs'; import { ControlInterface } from '@/interface/control'; import { UIActionTool,Util,ViewTool } from '@/utils'; import NavDataService from '@/service/app/navdata-service'; import AppCenterService from "@service/app/app-center-service"; import IBIZOrderEntityService from '@/service/ibizorder/ibizorder-service'; import TreeExpService from './tree-exp-treeview-service'; import IBIZOrderUIService from '@/uiservice/ibizorder/ibizorder-ui-service'; import { Environment } from '@/environments/environment'; import UIService from '@/uiservice/ui-service'; @Component({ components: { } }) export default class TreeExpBase extends Vue implements ControlInterface { /** * 名称 * * @type {string} * @memberof TreeExpBase */ @Prop() public name?: string; /** * 视图通讯对象 * * @type {Subject<ViewState>} * @memberof TreeExpBase */ @Prop() public viewState!: Subject<ViewState>; /** * 应用上下文 * * @type {*} * @memberof TreeExpBase */ @Prop() public context!: any; /** * 视图参数 * * @type {*} * @memberof TreeExpBase */ @Prop() public viewparams!: any; /** * 视图操作参数(父级) * * @type {*} * @memberof TreeExpBase */ @Prop() public pViewCtx!: any; /** * 视图操作参数 * * @type {*} * @memberof TreeExpBase */ public viewCtx: any = {}; /** * 监听视图操作参数变化 * * @type {*} * @memberof TreeExpBase */ @Watch('pViewCtx', { immediate: true }) public onViewCtxChange(newVal: any, oldVal: any) { Object.assign(this.viewCtx, newVal, { xData: this, ctrl: this }); } /** * 视图状态事件 * * @public * @type {(Subscription | undefined)} * @memberof TreeExpBase */ public viewStateEvent: Subscription | undefined; /** * 获取部件类型 * * @returns {string} * @memberof TreeExpBase */ public getControlType(): string { return 'TREEVIEW' } /** * 计数器服务对象集合 * * @type {Array<*>} * @memberof TreeExpBase */ public counterServiceArray:Array<any> = []; /** * 建构部件服务对象 * * @type {TreeExpService} * @memberof TreeExpBase */ public service: TreeExpService = new TreeExpService({ $store: this.$store }); /** * 实体服务对象 * * @type {IBIZOrderService} * @memberof TreeExpBase */ public appEntityService: IBIZOrderEntityService = new IBIZOrderEntityService({ $store: this.$store }); /** * 转化数据 * * @param {any} args * @memberof TreeExpBase */ public transformData(args: any) { let _this: any = this; if(_this.service && _this.service.handleRequestData instanceof Function && _this.service.handleRequestData('transform',_this.context,args)){ return _this.service.handleRequestData('transform',_this.context,args)['data']; } } /** * 关闭视图 * * @param {any} args * @memberof TreeExpBase */ public closeView(args: any): void { let _this: any = this; _this.$emit('closeview', [args]); } /** * 计数器刷新 * * @memberof TreeExpBase */ public counterRefresh(){ const _this:any =this; if(_this.counterServiceArray && _this.counterServiceArray.length >0){ _this.counterServiceArray.forEach((item:any) =>{ if(item.refreshData && item.refreshData instanceof Function){ item.refreshData(); } }) } } /** * 处理部件事件 * * @memberof TreeExpBase */ public async handleCtrlEvents(eventName: string, args: any = {}): Promise<boolean> { const actionData = { data: this.getData() || {}, context: Util.deepCopy(this.context), viewparams: Util.deepCopy(this.viewparams), xData: this } let result: boolean = true; Object.assign(actionData, args); if ('selectionchange'.indexOf(eventName) !== -1) { result = await this.execute_treeexpbar_selectionchange_ctrl_logic(actionData) && result; } if ('load'.indexOf(eventName) !== -1) { result = await this.execute_treeexpbar_load_ctrl_logic(actionData) && result; } if (!result) { return false; } this.$emit(eventName, actionData); return true; } /** * 部件逻辑 -- treeexpbar_selectionchange * * @param {string} eventName 部件事件名称 * @param {any[]} data 数据 * @param {*} event 源事件对象 * @memberof TreeExpBase */ public async execute_treeexpbar_selectionchange_ctrl_logic(actionData: any): Promise<boolean> { console.log('暂未支持 CUSTOM 类型'); return true; } /** * 部件逻辑 -- treeexpbar_load * * @param {string} eventName 部件事件名称 * @param {any[]} data 数据 * @param {*} event 源事件对象 * @memberof TreeExpBase */ public async execute_treeexpbar_load_ctrl_logic(actionData: any): Promise<boolean> { console.log('暂未支持 CUSTOM 类型'); return true; } /** * 获取多项数据 * * @returns {any[]} * @memberof TreeExpBase */ public getDatas(): any[] { return [this.currentselectedNode]; } /** * 获取单项树 * * @returns {*} * @memberof TreeExpBase */ public getData(): any { return this.currentselectedNode; } /** * 是否单选 * * @type {boolean} * @memberof TreeExpBase */ @Prop({ default: true }) public isSingleSelect!: boolean; /** * 是否默认选中第一条数据 * * @type {boolean} * @memberof TreeExpBase */ @Prop({ default: false }) public isSelectFirstDefault!: boolean; /** * 枝干节点是否可用(具有数据能力,可抛出) * * @type {string} * @memberof TreeExpBase */ @Prop({default:true}) public isBranchAvailable!: boolean; /** * 显示处理提示 * * @type {boolean} * @memberof TreeExpBase */ @Prop({ default: true }) public showBusyIndicator?: boolean; /** * 初始化完成 * * @type {boolean} * @memberof TreeExpBase */ public inited: boolean = false; /** * 已选中数据集合 * * @type {*} * @memberof TreeExpBase */ public selectedNodes: any = []; /** * 当前选中数据项 * * @type {*} * @memberof TreeExpBase */ public currentselectedNode: any = {}; /** * 选中数据字符串 * * @type {string} * @memberof TreeExpBase */ @Prop() public selectedData!: string; /** * 选中值变化 * * @param {*} newVal * @param {*} oldVal * @memberof TreeExpBase */ @Watch('selectedData') public onValueChange(newVal: any, oldVal: any) { this.echoselectedNodes = newVal ? this.isSingleSelect ? [JSON.parse(newVal)[0]] : JSON.parse(newVal) : []; this.selectedNodes = []; if(this.echoselectedNodes.length > 0){ let AllnodesObj = (this.$refs.treeexpbar_tree as any).store.nodesMap; let AllnodesArray : any[] = []; for (const key in AllnodesObj) { if (AllnodesObj.hasOwnProperty(key)) { AllnodesArray.push(AllnodesObj[key].data); } } this.setDefaultSelection(AllnodesArray); } } /** * 回显选中数据集合 * * @type {*} * @memberof TreeExpBase */ public echoselectedNodes:any[] = this.selectedData ? ( this.isSingleSelect ? [JSON.parse(this.selectedData)[0]] : JSON.parse(this.selectedData)) : []; /** * 部件行为--update * * @type {string} * @memberof TreeExpBase */ @Prop() public updateAction!: string; /** * 部件行为--fetch * * @type {string} * @memberof TreeExpBase */ @Prop() public fetchAction!: string; /** * 部件行为--remove * * @type {string} * @memberof TreeExpBase */ @Prop() public removeAction!: string; /** * 部件行为--load * * @type {string} * @memberof TreeExpBase */ @Prop() public loadAction!: string; /** * 部件行为--create * * @type {string} * @memberof TreeExpBase */ @Prop() public createAction!: string; /** * 过滤属性 * * @type {string} * @memberof TreeExpBase */ public srfnodefilter: string = ''; /** * 默认输出图标 * * @type {boolean} * @memberof TreeExpBase */ public isOutputIconDefault: boolean = true; /** * 数据展开主键 * * @type {string[]} * @memberof TreeExpBase */ @Provide() public expandedKeys: string[] = []; /** * 应用状态事件 * * @public * @type {(Subscription | undefined)} * @memberof TreeExpBase */ public appStateEvent: Subscription | undefined; /** * 树节点上下文菜单集合 * * @type {string[]} * @memberof TreeExpBase */ public actionModel: any = { } /** * 备份树节点上下文菜单 * * @type any * @memberof MainTreeBase */ public copyActionModel:any; /** * 选中数据变更事件 * * @public * @param {*} data * @param {*} data 当前节点对应传入对象 * @param {*} checkedState 树目前选中状态对象 * @memberof TreeExpBase */ public onCheck(data: any, checkedState: any) { // 处理多选数据 if(!this.isSingleSelect){ let leafNodes = checkedState.checkedNodes.filter((item:any) => item.leaf); const selectedNodes = JSON.parse(JSON.stringify(leafNodes)); this.handleCtrlEvents('onselectionchange', { action: 'SelectionChange', data: selectedNodes }).then((res: boolean) => { if (res) { this.selectedNodes = selectedNodes; this.$emit('selectionchange', this.selectedNodes); } }) } } /** * 选中数据变更事件 * * @public * @param {*} data 节点对应传入对象 * @param {*} node 节点对应node对象 * @memberof TreeExpBase */ public selectionChange(data: any, node: any) { // 禁用项处理 if (data.disabled){ node.isCurrent = false; return; } // 只处理最底层子节点 if(this.isBranchAvailable || data.leaf) { this.handleCtrlEvents('onselectionchange', { action: 'SelectionChange', data: data }).then((res: boolean) => { if (!res) { return; } this.currentselectedNode = JSON.parse(JSON.stringify(data)); // 单选直接替换 if(this.isSingleSelect){ this.selectedNodes = [this.currentselectedNode]; this.$emit('selectionchange', this.selectedNodes); } // 多选用check方法 }); } } /** * Vue声明周期(处理组件的输入属性) * * @memberof TreeExpBase */ public created() { this.afterCreated(); } /** * 执行created后的逻辑 * * @memberof TreeExpBase */ public afterCreated(){ if (this.viewState) { this.viewStateEvent = this.viewState.subscribe(({ tag, action, data }) => { if (!Object.is(tag, this.name)) { return; } if (Object.is('load', action)) { this.inited = false; this.$nextTick(() => { this.inited = true; }); } if (Object.is('filter', action)) { this.srfnodefilter = data.srfnodefilter; this.refresh_all(); } if (Object.is('refresh_parent', action)) { this.refresh_parent(); } if (Object.is('refresh_current', action)) { this.refresh_current(); } }); } if(AppCenterService && AppCenterService.getMessageCenter()){ this.appStateEvent = AppCenterService.getMessageCenter().subscribe(({ name, action, data }) =>{ if(!Object.is(name,"IBIZOrder")){ return; } if(Object.is(action,'appRefresh')){ this.refresh([data]); } }) } } /** * 对树节点进行筛选操作 * @memberof OrderTree */ public filterNode(value:any, data:any) { if (!value) return true; return data.text.indexOf(value) !== -1; } /** * vue 生命周期 * * @memberof TreeExpBase */ public destroyed() { this.afterDestroy(); } /** * 执行destroyed后的逻辑 * * @memberof TreeExpBase */ public afterDestroy() { if (this.viewStateEvent) { this.viewStateEvent.unsubscribe(); } if(this.appStateEvent){ this.appStateEvent.unsubscribe(); } } /** * 刷新数据 * * @memberof TreeExpBase */ public refresh_all(): void { this.inited = false; this.$nextTick(() => { this.inited = true; }); } /** * 刷新当前节点 * * @memberof TreeExpBase */ public refresh_current(): void { if (Object.keys(this.currentselectedNode).length === 0) { return; } const tree: any = this.$refs.treeexpbar_tree; const node: any = tree.getNode(this.currentselectedNode.id); if (!node || !node.parent) { return; } let curNode:any = {}; curNode = Util.deepObjectMerge(curNode,node); let tempContext:any = {}; if(curNode.data && curNode.data.srfappctx){ Object.assign(tempContext,curNode.data.srfappctx); }else{ Object.assign(tempContext,this.context); } const id: string = node.key ? node.key : '#'; const param: any = { srfnodeid: id }; this.refresh_node(tempContext,param, false); } /** * 刷新父节点 * * @memberof TreeExpBase */ public refresh_parent(): void { if (Object.keys(this.currentselectedNode).length === 0) { return; } const tree: any = this.$refs.treeexpbar_tree; const node: any = tree.getNode(this.currentselectedNode.id); if (!node || !node.parent) { return; } let curNode:any = {}; const { parent: _parent } = node; curNode = Util.deepObjectMerge(curNode,_parent); let tempContext:any = {}; if(curNode.data && curNode.data.srfappctx){ Object.assign(tempContext,curNode.data.srfappctx); }else{ Object.assign(tempContext,this.context); } const id: string = _parent.key ? _parent.key : '#'; const param: any = { srfnodeid: id }; this.refresh_node(tempContext,param, true); } /** * 数据加载 * * @param {*} node * @memberof TreeExpBase */ public load(node: any = {}, resolve?: any) { if (node.data && node.data.children) { resolve(node.data.children); return; } const params: any = { srfnodeid: node.data && node.data.id ? node.data.id : "#", srfnodefilter: this.srfnodefilter }; let tempViewParams:any = JSON.parse(JSON.stringify(this.viewparams)); let curNode:any = {}; curNode = Util.deepObjectMerge(curNode,node); let tempContext:any = this.computecurNodeContext(curNode); if(curNode.data && curNode.data.srfparentdename){ Object.assign(tempContext,{srfparentdename:curNode.data.srfparentdename}); Object.assign(tempViewParams,{srfparentdename:curNode.data.srfparentdename}); } if(curNode.data && curNode.data.srfparentkey){ Object.assign(tempContext,{srfparentkey:curNode.data.srfparentkey}); Object.assign(tempViewParams,{srfparentkey:curNode.data.srfparentkey}); } Object.assign(params,{ viewparams: tempViewParams }); this.handleCtrlEvents('onbeforeload', { data: node.data }).then((beforeLoadResult: boolean) => { if (!beforeLoadResult) { return; } this.service.getNodes(tempContext,params).then((response: any) => { if (!response || response.status !== 200) { this.handleCtrlEvents('onloaderror', { data: node.data }).then((loadErrorResult: boolean) => { if (!loadErrorResult) { return; } this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: response.info }); resolve([]); return; }) } this.handleCtrlEvents('onloaderror', { data: node.data }).then((loadSuccessResult: boolean) => { if (!loadSuccessResult) { resolve([]); return; } const _items = response.data; this.formatExpanded(_items); resolve([..._items]); let isRoot = Object.is(node.level,0); let isSelectedAll = node.checked; this.setDefaultSelection(_items, isRoot, isSelectedAll); this.$emit("load", _items); }); }).catch((response: any) => { this.handleCtrlEvents('onloaderror', { data: node.data }).then((loadErrorResult: boolean) => { if (!loadErrorResult) { return; } resolve([]); if (response && response.status === 401) { return; } this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: response.info }); }); }); }) } /** * 计算当前节点的上下文 * * @param {*} curNode 当前节点 * @memberof TreeExpBase */ public computecurNodeContext(curNode:any){ let tempContext:any = {}; if(curNode && curNode.data && curNode.data.srfappctx){ tempContext = JSON.parse(JSON.stringify(curNode.data.srfappctx)); }else{ tempContext = JSON.parse(JSON.stringify(this.context)); } return tempContext; } /** * 刷新功能 * * @param {any} args * @memberof TreeExpBase */ public refresh(args?: any): void { this.refresh_all(); } /** * 刷新节点 * * @public * @param {*} [curContext] 当前节点上下文 * @param {*} [arg={}] 当前节点附加参数 * @param {boolean} parentnode 是否是刷新父节点 * @memberof TreeExpBase */ public refresh_node(curContext:any,arg: any = {}, parentnode: boolean): void { const { srfnodeid: id } = arg; Object.assign(arg, { viewparams: this.viewparams }); this.handleCtrlEvents('onbeforerefreshnode', { data: arg }).then((beforeRefreshRes: boolean) => { if (!beforeRefreshRes) { return; } const get: Promise<any> = this.service.getNodes(JSON.parse(JSON.stringify(curContext)), arg); get.then((response: any) => { if (!response || response.status !== 200) { this.handleCtrlEvents('onrefreshnodeerror', { data: response && response.data ? response.data : {} }).then((errorRes: boolean) => { if (!errorRes) { return; } this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: response.info }); return; }) } const _items = [...response.data]; this.handleCtrlEvents('onrefreshnodesuccess', { data: _items }).then((successRes: boolean) => { if (!successRes) { return; } this.formatExpanded(_items); const tree: any = this.$refs.treeexpbar_tree; tree.updateKeyChildren(id, _items); if (parentnode) { this.currentselectedNode = {}; } this.$forceUpdate(); this.setDefaultSelection(_items); }) }).catch((response: any) => { this.handleCtrlEvents('onrefreshnodeerror', { data: response && response.data ? response.data : {} }).then((errorRes: boolean) => { if (!errorRes) { return; } if (response && response.status === 401) { return; } this.$Notice.error({ title: (this.$t('app.commonWords.wrong') as string), desc: response.info }); }) }); }) } /** * 默认展开节点 * * @public * @param {any[]} items * @returns {any[]} * @memberof TreeExpBase */ public formatExpanded(items: any[]): any[] { const data: any[] = []; items.forEach((item) => { if (item.expanded || (item.children && item.children.length > 0)) { this.expandedKeys.push(item.id); } }); return data; } /** * 设置默认选中,回显数项,选中所有子节点 * * @param {any[]} items 当前节点所有子节点集合 * @param {boolean} isRoot 是否是加载根节点 * @param {boolean} isSelectedAll 是否选中所有子节点 * @memberof TreeExpBase */ public setDefaultSelection(items: any[], isRoot: boolean = false, isSelectedAll: boolean = false): void { if(items.length == 0){ return; } let defaultData: any; //在导航视图中,如已有选中数据,则右侧展开已选中数据的视图,如无选中数据则默认选中第一条 if(this.isSelectFirstDefault){ if(this.isSingleSelect){ let index: number = -1; if(this.selectedNodes && this.selectedNodes.length > 0){ this.selectedNodes.forEach((select: any) => { index = items.findIndex((item: any) => Object.is(select.srfkey,item.srfkey)); }); } if(index === -1) { if(isRoot){ index = 0; }else{ return; } } defaultData = items[index]; this.setTreeNodeHighLight(defaultData); this.currentselectedNode = JSON.parse(JSON.stringify(defaultData)); if(this.isBranchAvailable || defaultData.leaf){ this.selectedNodes = [this.currentselectedNode]; this.$emit('selectionchange', this.selectedNodes); } } } // 已选数据的回显 if(this.echoselectedNodes && this.echoselectedNodes.length > 0){ let checkedNodes = items.filter((item:any)=>{ return this.echoselectedNodes.some((val:any)=> { if(Object.is(item.srfkey,val.srfkey) && Object.is(item.srfmajortext,val.srfmajortext)){ val.used = true; this.selectedNodes.push(val); this.$emit('selectionchange', this.selectedNodes); return true; } }); }); if(checkedNodes.length > 0){ this.echoselectedNodes = this.echoselectedNodes.filter((item:any)=> !item.used); // 父节点选中时,不需要执行这段,会选中所有子节点 if(!isSelectedAll){ if(this.isSingleSelect){ this.setTreeNodeHighLight(checkedNodes[0]); this.currentselectedNode = JSON.parse(JSON.stringify(checkedNodes[0])); this.selectedNodes = [this.currentselectedNode]; }else{ this.selectedNodes = this.selectedNodes.concat(checkedNodes); const tree: any = this.$refs.treeexpbar_tree; tree.setCheckedNodes(this.selectedNodes); } } } } // 父节点选中时,选中所有子节点 if(isSelectedAll){ let leafNodes = items.filter((item:any) => item.leaf); this.selectedNodes = this.selectedNodes.concat(leafNodes); this.$emit('selectionchange', this.selectedNodes); } } /** * 绘制右击菜单 * * @param {*} node * @returns * @memberof TreeExpBase */ public renderContextMenu(node: any) { let content; if (node && node.data) { const data: any = JSON.parse(JSON.stringify(node.data)); this.currentselectedNode = { ...data }; const tags: string[] = data.id.split(';'); let copyActionModel:any =Util.deepCopy(this.actionModel); } return content; } /** * 显示上下文菜单 * * @param data 节点数据 * @param event 事件源 * @memberof TreeExpBase */ public showContext(data:any,event:any){ let _this:any = this; this.copyActionModel = {}; const tags: string[] = data.id.split(';'); Object.values(this.actionModel).forEach((item:any) =>{ if(Object.is(item.nodeOwner,tags[0])){ this.copyActionModel[item.name] = item; } }) if(Object.keys(this.copyActionModel).length === 0){ return; } this.computeNodeState(data,data.nodeType,data.appEntityName).then((result:any) => { let flag:boolean = false; if(Object.values(result).length>0){ flag =Object.values(result).some((item:any) =>{ return item.visabled === true; }) } if(flag){ (_this.$refs[data.id] as any).showContextMenu(event.clientX, event.clientY); } }); } /** * 计算节点右键权限 * * @param {*} node 节点数据 * @param {*} nodeType 节点类型 * @param {*} appEntityName 应用实体名称 * @returns * @memberof TreeExpBase */ public async computeNodeState(node:any,nodeType:string,appEntityName:string) { if(Object.is(nodeType,"STATIC")){ return this.copyActionModel; } let service:any = await this.appEntityService.getService(appEntityName); if(this.copyActionModel && Object.keys(this.copyActionModel).length > 0) { if(service['Get'] && service['Get'] instanceof Function){ let tempContext:any = Util.deepCopy(this.context); tempContext[appEntityName] = node.srfkey; let targetData = await service.Get(tempContext,{}, false); let uiservice:any = await new UIService().getService(appEntityName); let result: any[] = ViewTool.calcActionItemAuthState(targetData.data,this.copyActionModel,uiservice); return this.copyActionModel; }else{ console.warn("获取数据异常"); return this.copyActionModel; } } } /** * 设置选中高亮 * * @param {*} data * @memberof TreeExpBase */ public setTreeNodeHighLight(data: any): void { const tree: any = this.$refs.treeexpbar_tree; tree.setCurrentKey(data.id); } /** * 执行默认界面行为 * * @param {*} node * @memberof TreeExpBase */ public doDefaultAction(node: any) { if (node && node.data) { const data: any = node.data; const tags: string[] = data.id.split(';'); } this.$emit('nodedblclick', this.selectedNodes); } } </script> <style lang='less'> @import './tree-exp-treeview.less'; </style>