import { ViewTool, Util, ListControlInterface, LogUtil } from '@ibizstudio/runtime'; import { MDControlBase } from "./md-control-base"; import { AppViewLogicService } from '../app-service'; import { AppListService } from '../ctrl-service'; import { IPSDEList, IPSDEListDataItem, IPSDEListItem, IPSDEUIAction, IPSDEUIActionGroup, IPSUIAction, IPSUIActionGroupDetail } from '@ibizstudio/runtime'; import { SortableEvent } from 'sortablejs'; import { acc } from '@ibizstudio/api'; /** * 列表部件基类 * * @export * @class ListControlBase * @extends {MDControlBase} */ export class ListControlBase extends MDControlBase implements ListControlInterface { /** * 列表的模型对象 * * @type {*} * @memberof ListControlBase */ declare controlInstance: IPSDEList; /** * 列表服务对象 * * @type {*} * @memberof ListControlBase */ declare service: any; /** * 分组数据 * * @type {Array} * @memberof ListControlBase */ groupData: Array = []; /** * 列表数据 * * @type {*} * @memberof ListControlBase */ items: Array = []; /** * 加载的数据是否附加在items之后 * * @type {boolean} * @memberof ListControlBase */ isAddBehind: boolean = false; /** * 默认排序方向 * * @readonly * @memberof ListControlBase */ minorSortDir: any = ''; /** * 默认排序应用实体属性 * * @readonly * @memberof ListControlBase */ minorSortPSDEF: any = ''; /** * 是否支持行次序调整 * * @author zhanghengfeng * @date 2023-07-05 14:07:11 * @type {boolean} */ enableRowEditOrder: boolean = false; /** * acc订阅标识 * * @author zhanghengfeng * @date 2023-07-06 20:07:09 * @type {string[]} */ accSubs: string[] = []; /** * 是否启用acc通知 * * @author zhanghengfeng * @date 2023-07-06 21:07:04 * @type {boolean} */ isEnableAcc: boolean = true; /** * 拖拽分组名 * * @author zhanghengfeng * @date 2023-07-06 21:07:28 * @type {string} */ groupName: string = Util.createUUID(); /** * 监听静态参数变化 * * @param {*} newVal * @param {*} oldVal * @memberof ListControlBase */ onStaticPropsChange(newVal: any, oldVal: any) { this.isSelectFirstDefault = newVal.isSelectFirstDefault; super.onStaticPropsChange(newVal, oldVal); } /** * 部件模型数据初始化实例 * * @memberof ListControlBase */ async ctrlModelInit(args?: any) { await super.ctrlModelInit(); if (!(this.Environment && this.Environment.isPreviewMode)) { this.service = new AppListService(this.controlInstance, this.context); await this.service.loaded(this.controlInstance); } this.minorSortPSDEF = this.controlInstance.getMinorSortPSAppDEField()?.codeName; this.minorSortDir = this.controlInstance.minorSortDir; this.limit = this.controlInstance?.pagingSize || this.limit; this.enableRowEditOrder = this.controlInstance.enableRowEditOrder; } /** * 监听可拖拽item项位置变化 * * @author zhanghengfeng * @date 2023-07-06 21:07:33 * @param {SortableEvent} evt */ async onDraggableItemPositionChange(evt: SortableEvent) { const { oldIndex, newIndex } = evt; if (oldIndex != null && newIndex != null) { const data = this.items[oldIndex]; this.items.splice(oldIndex, 1); this.items.splice(newIndex, 0, data); this.items.forEach((item, i) => { item.ordervalue = (i + 1) * 100; }); try { this.isEnableAcc = false; await this.service.updateBatch(this.context, this.items); } catch (error) { this.$throw(error); } finally { this.isEnableAcc = true; } } } /** * 初始化 * * @memberof ListControlBase */ ctrlInit(args?: any) { super.ctrlInit(); // 绑定this this.transformData = this.transformData.bind(this); this.remove = this.remove.bind(this); this.refresh = this.refresh.bind(this); this.onDraggableItemPositionChange = this.onDraggableItemPositionChange.bind(this); this.accSubs.push( acc.commandLocal( () => { if (this.isEnableAcc) { this.refresh(); } }, 'update', this.controlInstance.getPSAppDataEntity()?.codeName?.toUpperCase(), ), ); } /** * 执行destroyed后的逻辑 * * @author zhanghengfeng * @date 2023-07-06 20:07:28 */ ctrlDestroyed() { super.ctrlDestroyed(); this.accSubs.forEach(item => { acc.unsubscribeLocal(item); }); } viewStateAction(tag: string, action: string, data: any) { if (!Object.is(tag, this.name)) { return; } super.viewStateAction(tag, action, data); if (Object.is(action, 'load')) { this.curPage = 1; this.items = []; this.load(data); } if (Object.is(action, 'save')) { this.save(data); } if (Object.is(action, 'refresh')) { this.refresh(data); } } /** * 部件挂载完毕 * * @protected * @memberof ListControlBase */ ctrlMounted(): void { super.ctrlMounted(); const loadMoreCallBack: any = this.throttle(this.loadMore, 3000); this.$el.addEventListener('scroll', () => { if (this.$el.scrollTop + this.$el.clientHeight >= this.$el.scrollHeight) { loadMoreCallBack(); } }); } /** * 初始化界面行为模型 * * @type {*} * @memberof ListControlBase */ initCtrlActionModel() { if (this.controlInstance.getPSDEListItems() && (this.controlInstance.getPSDEListItems() as any)?.length > 0) { for (let index = 0; index < (this.controlInstance.getPSDEListItems() as any).length; index++) { const listItem: IPSDEListItem = (this.controlInstance.getPSDEListItems() as any)[index]; if (listItem.getPSDEUIActionGroup() && ((listItem.getPSDEUIActionGroup() as IPSDEUIActionGroup).getPSUIActionGroupDetails() as any)?.length > 0) { for (let index = 0; index < ((listItem.getPSDEUIActionGroup() as IPSDEUIActionGroup).getPSUIActionGroupDetails() as any).length; index++) { const uiActionDetail: IPSUIActionGroupDetail = ((listItem.getPSDEUIActionGroup() as IPSDEUIActionGroup).getPSUIActionGroupDetails() as any)[index]; if (uiActionDetail?.getPSUIAction()) { const uiAction: IPSDEUIAction = uiActionDetail.getPSUIAction() as IPSDEUIAction; if (uiAction) { const appUIAction: IPSDEUIAction = Util.deepCopy(uiAction) as IPSDEUIAction; this.actionModel[uiAction.uIActionTag] = Object.assign(appUIAction, { disabled: false, visabled: true, getNoPrivDisplayMode: appUIAction.noPrivDisplayMode ? appUIAction.noPrivDisplayMode : 6 }); } } } } } } } /** * 初始化数据映射 * * @memberof ListControlBase */ initDataMap() { const dataItems: IPSDEListDataItem[] | null = this.controlInstance.getPSDEListDataItems(); if (dataItems && dataItems.length > 0) { dataItems.forEach((dataItem: IPSDEListDataItem) => { this.dataMap.set(dataItem.name,{ customCode: dataItem.customCode ? true : false }); }); }; } /** * 列表数据加载 * * @param {*} [opt={}] 额外参数 * @returns {void} * @memberof ListControlBase */ load(opt: any = {}): void { if (!this.fetchAction) { this.$throw(this.$t('app.list.notconfig.fetchaction'),'load'); return; } const arg: any = { ...opt }; const page: any = {}; if (this.isEnablePagingBar) { Object.assign(page, { page: this.curPage - 1, size: this.limit }); } // 设置排序 if (Util.isExistAndNotEmpty(this.minorSortDir) && Util.isExistAndNotEmpty(this.minorSortPSDEF)) { const sort: string = this.minorSortPSDEF + ',' + this.minorSortDir; Object.assign(page, { sort: sort }); } Object.assign(arg, page); const parentdata: any = {}; this.ctrlEvent({ controlname: this.name, action: 'beforeload', data: parentdata, }); Object.assign(arg, parentdata); let tempViewParams: any = parentdata.viewparams ? parentdata.viewparams : opt ? opt : {}; if (this.viewparams) { Object.assign(tempViewParams, JSON.parse(JSON.stringify(this.viewparams))); } const _this: any = this; Object.assign(arg, { viewparams: tempViewParams }); let tempContext:any = JSON.parse(JSON.stringify(this.context)); this.onControlRequset('load', tempContext, arg); const post: Promise = this.service.search(this.fetchAction, tempContext, arg, this.showBusyIndicator); post.then( (response: any) => { _this.onControlResponse('load', response); if (!response || response.status !== 200) { this.$throw(response,'load'); return; } const data: any = response.data; if (!_this.isAddBehind) { _this.items = []; } if (data && data.length > 0) { let datas = JSON.parse(JSON.stringify(data)); datas.map((item: any) => { if (!item.srfchecked) { Object.assign(item, { srfchecked: 0 }); } }); _this.totalRecord = response.total; _this.items.push(...datas); } _this.isAddBehind = false; _this.items.forEach((item: any) => { Object.assign(item, _this.getActionState(item)); }) _this.ctrlEvent({ controlname: _this.name, action: 'load', data: _this.items, }); if (_this.isSelectFirstDefault) { if (_this.selections && _this.selections.length > 0) { _this.selections.forEach((select: any) => { const index = _this.items.findIndex((item: any) => Object.is(item.srfkey, select.srfkey)); if (index != -1) { _this.handleClick(_this.items[index]); } }); } else { _this.handleClick(_this.items[0]); } } if (this.isEnableGroup) { this.group(); } }, (response: any) => { _this.onControlResponse('load', response); this.$throw(response,'load'); } ) } /** * 删除 * * @param {any[]} items 删除数据 * @returns {Promise} * @memberof ListControlBase */ async remove(items: any[]): Promise { if (!this.removeAction) { this.$throw(`${this.name}${this.$t('app.list.notconfig.removeaction')}`,'remove'); return; } if (items.length === 0) { return; } let dataInfo = ''; items.forEach((record: any, index: number) => { let srfmajortext = record.srfmajortext; if (index < 5) { if (!Object.is(dataInfo, '')) { dataInfo += '、'; } dataInfo += srfmajortext; } else { return false; } }); if (items.length < 5) { dataInfo = dataInfo + this.$t('app.dataview.sum') + items.length + this.$t('app.dataview.data'); } else { dataInfo = dataInfo + '...' + this.$t('app.dataview.sum') + items.length + this.$t('app.dataview.data'); } const removeData = () => { let keys: any[] = []; items.forEach((data: any) => { keys.push(data.srfkey); }); let _removeAction = keys.length > 1 ? 'removeBatch' : this.removeAction; let tempContext:any = JSON.parse(JSON.stringify(this.context)); Object.assign(tempContext, { [this.appDeCodeName?.toLowerCase()]: keys.join(';') }); let arg: any = { [this.appDeCodeName.toLowerCase()]: keys.join(';') }; Object.assign(arg, { viewparams: this.viewparams }); this.onControlRequset('remove', tempContext, arg); const post: Promise = this.service.delete(_removeAction, tempContext, arg, this.showBusyIndicator); return new Promise((resolve: any, reject: any) => { post.then((response: any) => { this.onControlResponse('remove', response); if (!response || response.status !== 200) { this.$throw(this.$t('app.commonwords.deldatafail') + response.info,'remove'); return; } else { this.$success(this.$t('app.commonwords.deletesuccess'),'remove'); } //删除items中已删除的项 items.forEach((data: any) => { this.items.some((item: any, index: number) => { if (Object.is(item.srfkey, data.srfkey)) { this.items.splice(index, 1); return true; } }); }); this.ctrlEvent({ controlname: this.name, action: 'remove', data: null, }); this.selections = []; resolve(response); }).catch((response: any) => { this.onControlResponse('remove', response); this.$throw(response,'remove'); reject(response); }); }); }; dataInfo = dataInfo .replace(/[null]/g, '') .replace(/[undefined]/g, '') .replace(/[ ]/g, ''); this.$Modal.confirm({ title: (this.$t('app.commonwords.warning') as string), content: this.$t('app.grid.confirmdel') + dataInfo + this.$t('app.grid.notrecoverable'), onOk: () => { removeData(); }, onCancel: () => { }, }); return removeData; } /** * 保存 * * @param {*} args 额外参数 * @return {*} * @memberof ListControlBase */ async save(args: any) { let _this = this; let successItems: any = []; let errorItems: any = []; let errorMessage: any = []; for (const item of _this.items) { try { if (Object.is(item.rowDataState, 'create')) { if (!this.createAction) { this.$throw(`${this.controlInstance.codeName}` + (this.$t('app.list.notconfig.createaction') as string),'save'); } else { Object.assign(item, { viewparams: this.viewparams }); let tempContext:any = JSON.parse(JSON.stringify(this.context)); this.onControlRequset('create', tempContext, item); let response = await this.service.add(this.createAction, tempContext, item, this.showBusyIndicator); this.onControlResponse('create', response); successItems.push(JSON.parse(JSON.stringify(response.data))); } } else if (Object.is(item.rowDataState, 'update')) { if (!this.updateAction) { this.$throw(`${this.controlInstance.codeName}` + (this.$t('app.list.notconfig.updateaction') as string),'save'); } else { Object.assign(item, { viewparams: this.viewparams }); if (this.appDeCodeName && item[this.appDeCodeName]) { Object.assign(this.context, { [this.appDeCodeName]: item[this.appDeCodeName] }); } let tempContext:any = JSON.parse(JSON.stringify(this.context)); this.onControlRequset('update', tempContext, item); let response = await this.service.add(this.updateAction, tempContext, item, this.showBusyIndicator); this.onControlResponse('update', response); successItems.push(JSON.parse(JSON.stringify(response.data))); } } } catch (error) { this.onControlResponse('save', error); errorItems.push(JSON.parse(JSON.stringify(item))); errorMessage.push(error); } } this.$emit('ctrl-event', { controlname: this.name, action: "save", data: successItems }); this.refresh(); if (errorItems.length === 0) { if(args?.showResultInfo || (args && !args.hasOwnProperty('showResultInfo'))){ this.$success((this.$t('app.commonwords.savesuccess') as string),'save'); } } else { errorItems.forEach((item: any, index: number) => { this.$throw(item.majorentityname + (this.$t('app.commonwords.savefailed') as string) + '!','save'); this.$throw(errorMessage[index],'save'); }); } return successItems; } /** * 获取选中数据 * * @returns {any[]} * @memberof GridControlBase */ getSelection(): any[] { return this.selections; } /** * 清除当前所有选中状态 * * @memberof ListControlBase */ clearSelection() { this.items.map((item: any) => { Object.assign(item, { srfchecked: 0 }); }); } /** * 加载更多 * * @memberof ListControlBase */ loadMore() { if (this.totalRecord > this.items.length) { this.curPage = ++this.curPage; this.isAddBehind = true; this.load({}); } } /** * 刷新 * * @param {*} [args] 额外参数 * @memberof ListControlBase */ refresh(args?: any) { this.isAddBehind = false; this.load(args); } /** * 行单击事件 * * @param {*} args 行数据 * @memberof ListControlBase */ handleClick(args: any) { if (this.mDCtrlActiveMode === 1) { this.ctrlEvent({ controlname: this.name, action: 'rowclick', data: args }); return; } if (this.isSingleSelect) { this.clearSelection(); } args.srfchecked = Number(!args.srfchecked); this.selectchange(); this.$forceUpdate(); } /** * 触发事件 * @memberof ListControlBase * */ selectchange() { this.selections = []; this.items.map((item: any) => { if (item.srfchecked === 1) { this.selections.push(item); } }); this.ctrlEvent({ controlname: this.name, action: 'selectionchange', data: this.selections, }); } /** * 双击事件 * * @param {*} args 数据 * @memberof ListControlBase */ handleDblClick(args: any) { if (this.mDCtrlActiveMode !== 0) { this.ctrlEvent({ controlname: this.name as string, action: 'rowdblclick', data: args }); } } /** * 处理操作列点击 * * @param {*} data 行数据 * @param {*} event 事件源 * @param {*} item 列表项模型 * @param {*} detail 操作列模型 * @memberof ListControlBase */ handleActionClick(data: any, event: any, item: any, detail: any) { AppViewLogicService.getInstance().executeViewLogic(this.getViewLogicTag('list', item.dataItemName, detail.name), event, this, data, (this.controlInstance?.getPSAppViewLogics() as any)); } /** * 面板数据变化处理事件 * @param {any} item 当前列数据 * @param {any} $event 面板事件数据 * * @memberof ListControlBase */ onPanelDataChange(item: any, $event: any) { Object.assign(item, $event, { rowDataState: 'update' }); } /** * 计算部件所需参数 * * @param {*} controlInstance 部件模型对象 * @param {*} item 列表行数据 * @returns * @memberof ListControlBase */ computeTargetCtrlData(controlInstance: any, item?: any) { const { targetCtrlName, targetCtrlParam, targetCtrlEvent } = super.computeTargetCtrlData(controlInstance); Object.assign(targetCtrlParam.dynamicProps, { navdatas: [item], }) Object.assign(targetCtrlParam.staticProps, { transformData: this.transformData, opendata: this.opendata, newdata: this.newdata, remove: this.remove, refresh: this.refresh, dataMap: this.dataMap, }) targetCtrlEvent['ctrl-event'] = ({ controlname, action, data }: { controlname: string, action: string, data: any }) => { this.onCtrlEvent(controlname, action, { item: item, data: data }); }; return { targetCtrlName, targetCtrlParam, targetCtrlEvent }; } /** * 获取界面行为权限状态 * * @param {*} data 当前列表行数据 * @memberof ListControlBase */ getActionState(data: any) { let tempActionModel: any = JSON.parse(JSON.stringify(this.actionModel)); let targetData: any = this.transformData(data); ViewTool.calcActionItemAuthState(targetData, tempActionModel, this.appUIService); return tempActionModel; } /** * 节流 * * @param {*} fn 方法 * @param {number} wait 等待时间 * @return {*} * @memberof ListControlBase */ throttle(fn: any, wait: number) { let time = 0; return () => { let now = Date.now(); let args = arguments; if (now - time > wait) { fn.apply(this, args); time = now; } }; } drawGroup() { let data:Array = [...this.items]; let groups:Array = []; data.forEach((item: any)=>{ if(item.hasOwnProperty(this.groupField)){ groups.push(item[this.groupField]); } }); groups = [...new Set(groups)]; if(groups.length == 0){ LogUtil.warn('分组数据无效') } let groupTree:Array = []; groups.forEach((group: any,i: number)=>{ let children:Array = []; data.forEach((item: any,j: number)=>{ if(Object.is(group,item[this.groupField])){ children.push(item); } }); group = group ? group : this.$t('app.commonWords.other'); const tree: any ={ group: group, children: children } groupTree.push(tree); }); this.groupData = [...groupTree]; } async drawCodelistGroup() { if (!this.groupCodeList || !this.groupCodeList.codeName) { return; } let groupTree:Array = []; let data:Array = [...this.items]; let groupCodelist: any = await this.codeListService.getDataItems({ tag: this.groupCodeList.codeName, type: this.groupCodeList.codeListType, context: this.context, viewparams: this.viewparams }); if(groupCodelist.length == 0){ console.warn("分组数据无效"); } groupCodelist.forEach((group: any,i: number)=>{ let children:Array = []; data.forEach((item: any,j: number)=>{ if (Object.is(group.value, item[this.groupField])) { children.push(item); } }); const tree: any ={ group: group.label, children: children } groupTree.push(tree); }); let child:Array = []; data.forEach((item: any)=>{ let i: number = 0; i = groupCodelist.findIndex((group: any) => Object.is(group.value, item[this.groupField])); if(i < 0){ child.push(item); } }) const Tree: any = { group: this.$t('app.commonWords.other'), children: child } if(child && child.length > 0){ groupTree.push(Tree); } this.groupData = [...groupTree]; } }