From 93ed731db70ede46162cc32d4fe3bcfebf13177c Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Thu, 31 Jul 2025 15:58:26 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E6=8B=96=E6=8B=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/CHANGELOG.md | 4 + .../control/data-view/data-view.controller.ts | 252 +++++++++++++- .../control/data-view/data-view.service.ts | 26 +- .../control/kanban/kanban.controller.ts | 307 +++--------------- .../control/kanban/kanban.service.ts | 49 +-- .../control/i-api-data-view-control.state.ts | 10 + .../api/state/control/i-api-kanban.state.ts | 8 - packages/runtime/src/locale/en/index.ts | 10 +- packages/runtime/src/locale/zh-CN/index.ts | 8 +- 9 files changed, 336 insertions(+), 338 deletions(-) diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 3e6f032ea..f67c3ac67 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 新增卡片拖拽功能 + ## [0.7.41-alpha.16] - 2025-07-30 ### Added diff --git a/packages/runtime/src/controller/control/data-view/data-view.controller.ts b/packages/runtime/src/controller/control/data-view/data-view.controller.ts index b991776fc..fff55622f 100644 --- a/packages/runtime/src/controller/control/data-view/data-view.controller.ts +++ b/packages/runtime/src/controller/control/data-view/data-view.controller.ts @@ -1,6 +1,12 @@ -import { RuntimeModelError, isElementSame } from '@ibiz-template/core'; +/* eslint-disable no-nested-ternary */ +import { + clone, + DataTypes, + isElementSame, + RuntimeModelError, +} from '@ibiz-template/core'; import { IDEDataView, IUIActionGroupDetail } from '@ibiz/model-core'; -import { clone, isNil } from 'ramda'; +import { isNil } from 'ramda'; import { createUUID, isBoolean } from 'qx-util'; import { ISortItem, @@ -11,6 +17,7 @@ import { IDataViewControlState, IDataViewControlEvent, IDataViewControlController, + IDragChangeInfo, } from '../../../interface'; import { ControlVO } from '../../../service'; import { UIActionUtil } from '../../../ui-action'; @@ -22,7 +29,7 @@ import { ButtonContainerState, } from '../../utils'; import { DataViewControlService } from './data-view.service'; -import { getParentTextAppDEFieldId } from '../../../model'; +import { calcDeCodeNameById, getParentTextAppDEFieldId } from '../../../model'; export class DataViewControlController< T extends IDEDataView = IDEDataView, @@ -98,6 +105,7 @@ export class DataViewControlController< this.state.size = this.model.pagingSize || 20; this.state.singleSelect = this.model.singleSelect === true; this.state.sortItems = []; + this.state.updating = false; this.state.collapseKeys = []; const { enablePagingBar } = this.model; this.state.enablePagingBar = enablePagingBar; @@ -298,6 +306,8 @@ export class DataViewControlController< async afterLoad(args: MDCtrlLoadParams, items: IData[]): Promise { super.afterLoad(args, items); + // 每次加载回来先本地排序,把数据的排序属性规范一下 + this.sortItems(this.state.items); await this.initGroupCodeListItems(); await this.handleDataGroup(); await this.initGroupActionStates(); @@ -711,4 +721,240 @@ export class DataViewControlController< this.state.collapseKeys = this.state.groups.map(x => x.key.toString()); } } + + /** + * @description 本地排序items + * @param {IData[]} items + * @returns {*} {void} + * @memberof DataViewControlController + */ + sortItems(items: IData[]): void { + const { minorSortAppDEFieldId, minorSortDir } = this.model; + if (!minorSortAppDEFieldId || !minorSortDir) return; + const sortField = this.dataEntity?.appDEFields?.find( + _item => _item.codeName === minorSortAppDEFieldId, + ); + if (!sortField || !DataTypes.isNumber(sortField.stdDataType!)) { + ibiz.log.warn( + ibiz.i18n.t('runtime.controller.common.md.invalidSortType'), + ); + return; + } + const isAsc = minorSortDir === 'ASC'; + // 格式化排序属性的值 + items.forEach(item => { + const sortValue = item[minorSortAppDEFieldId]; + if (isNil(sortValue)) item[minorSortAppDEFieldId] = 0; + }); + // 排序 + items.sort((a, b) => + isAsc + ? a[minorSortAppDEFieldId] - b[minorSortAppDEFieldId] + : b[minorSortAppDEFieldId] - a[minorSortAppDEFieldId], + ); + } + + /** + * @description 计算移动数据参数 + * @protected + * @param {number} fromIndex 变更前的索引位置 + * @param {number} toIndex 变更后的索引位置 + * @param {IData} draggedItem 拖拽数据项 + * @param {IData[]} targetArray 数据集 + * @param {boolean} isCrossGroup 是否切换分组 + * @returns {*} {IData} + * @memberof DataViewControlController + */ + protected computeMoveDataParam( + fromIndex: number, + toIndex: number, + draggedItem: IData, + targetArray: IData[], + isCrossGroup: boolean, + ): IData { + let moveData = {}; + const { minorSortAppDEFieldId } = this.model; + if (!minorSortAppDEFieldId) return moveData; + const targetItem = targetArray[toIndex]; + if (!targetItem) { + let tempArray: IData[] = []; + if (targetArray.length > 0) { + tempArray = targetArray; + } + if (tempArray.length > 0) { + const maxItem = tempArray.reduce((prev, curr) => { + const sortCondition = + prev[minorSortAppDEFieldId] > curr[minorSortAppDEFieldId]; + if ( + sortCondition && + prev[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + return prev; + } + if ( + !sortCondition && + curr[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + return curr; + } + return prev; + }); + if ( + maxItem && + maxItem[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + moveData = { + srftargetkey: maxItem.srfkey, + srfmovetype: 'MOVEAFTER', + }; + } + } + } else { + moveData = { + srftargetkey: targetItem.srfkey, + srfmovetype: + toIndex < targetArray.length - 1 + ? 'MOVEBEFORE' + : isCrossGroup + ? 'MOVEBEFORE' + : 'MOVEAFTER', + }; + } + return moveData; + } + + /** + * @description 移动并排序数据 + * @param {ControlVO} draggedItem + * @param {IData} moveMeta + * @returns {*} {Promise} + * @memberof DataViewControlController + */ + async moveOrderItem(draggedItem: ControlVO, moveMeta: IData): Promise { + try { + this.state.updating = true; + const { minorSortAppDEFieldId } = this.model; + if (!minorSortAppDEFieldId) + return ibiz.log.error( + ibiz.i18n.t('runtime.controller.common.md.sortingProperties'), + ); + const deName = calcDeCodeNameById(this.model.appDataEntityId!); + const tempContext = this.context.clone(); + tempContext[deName] = draggedItem.srfkey; + if (!moveMeta.srftargetkey || !moveMeta.srfmovetype) + return ibiz.log.error( + ibiz.i18n.t('runtime.controller.common.md.computeMoveMetaError'), + ); + const res = await this.service.moveOrderItem( + tempContext, + draggedItem, + moveMeta, + ); + if (res.ok) { + // 通知实体数据变更 + this.emitDEDataChange('update', res.data); + res.data.forEach(_item => { + const item = this.state.items.find(x => x.srfkey === _item.srfkey); + if (item) item[minorSortAppDEFieldId] = _item[minorSortAppDEFieldId]; + }); + await this.afterLoad({}, this.state.items); + } + } finally { + this.state.updating = false; + } + } + + /** + * @description 批量更新修改项 + * @param {ControlVO[]} changedItems + * @returns {*} {Promise} + * @memberof DataViewControlController + */ + async updateChangedItems(changedItems: ControlVO[]): Promise { + try { + this.state.updating = true; + await Promise.all( + changedItems.map(async item => { + // 往上下文添加主键 + const deName = calcDeCodeNameById(this.model.appDataEntityId!); + const tempContext = this.context.clone(); + tempContext[deName] = item.srfkey; + // 调用接口修改数据 + const res = await this.service.update(tempContext, item); + // 更新完之后更新state里的数据。 + if (res.ok) { + // 通知实体数据变更 + this.emitDEDataChange('update', res.data); + const index = this.state.items.findIndex( + x => x.srfkey === item.srfkey, + ); + this.state.items.splice(index, 1, res.data); + } + }), + ); + } finally { + this.state.updating = false; + await this.afterLoad({}, this.state.items); + } + } + + /** + * @description 拖拽变更 + * @param {IDragChangeInfo} info + * @returns {*} {Promise} + * @memberof DataViewControlController + */ + async onDragChange(info: IDragChangeInfo): Promise { + const { from, to, fromIndex, toIndex } = info; + if (!this.enableEditGroup && from !== to) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.adjustmentsGroup'), + ); + if (!this.enableEditOrder && from === to) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.noAllowReorder'), + ); + + const { groupAppDEFieldId, moveControlAction, minorSortAppDEFieldId } = + this.model; + const fromGroup = this.state.groups.find(x => x.key === from); + const toGroup = this.state.groups.find(x => x.key === to); + const draggedItem = clone( + fromGroup?.children[fromIndex] || this.state.items[fromIndex], + ); + + // 分组变更 + if (from !== to && groupAppDEFieldId) draggedItem[groupAppDEFieldId] = to; + + // 仅变更分组 + if (!this.enableEditOrder) { + await this.updateChangedItems([draggedItem] as ControlVO[]); + } else { + // 排序变更 + if (!minorSortAppDEFieldId) + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.controller.common.md.sortingProperties'), + ); + const moveAction = moveControlAction?.appDEMethodId; + if (!moveAction) + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.controller.common.md.noMoveDataCconfig'), + ); + // 存在移动数据行为,先变更分组再变更排序 + if (from !== to) { + await this.updateChangedItems([draggedItem] as ControlVO[]); + } + const originArr = toGroup?.children || this.state.items; + const params = this.computeMoveDataParam( + fromIndex, + toIndex, + draggedItem, + originArr, + info.from !== info.to, + ); + await this.moveOrderItem(draggedItem as ControlVO, params); + } + } } diff --git a/packages/runtime/src/controller/control/data-view/data-view.service.ts b/packages/runtime/src/controller/control/data-view/data-view.service.ts index 020c60292..3ed3260d2 100644 --- a/packages/runtime/src/controller/control/data-view/data-view.service.ts +++ b/packages/runtime/src/controller/control/data-view/data-view.service.ts @@ -1,5 +1,6 @@ import { IDEDataView } from '@ibiz/model-core'; -import { MDControlService, UIMapField } from '../../../service'; +import { IHttpResponse, clone } from '@ibiz-template/core'; +import { ControlVO, MDControlService, UIMapField } from '../../../service'; /** * 数据视图(卡片)部件服务 @@ -11,6 +12,29 @@ import { MDControlService, UIMapField } from '../../../service'; export class DataViewControlService< T extends IDEDataView = IDEDataView, > extends MDControlService { + /** + * @description 移动并排序数据 + * @param {IContext} context + * @param {ControlVO} data + * @param {IData} args + * @returns {*} {Promise>} + * @memberof DataViewControlService + */ + async moveOrderItem( + context: IContext, + data: ControlVO, + args: IData, + ): Promise> { + const moveAction = this.model.moveControlAction!.appDEMethodId!; + const params = clone(data.getOrigin()); + Object.assign(params, args); + let res = await this.exec(moveAction, context, params, { + srfupdateitem: true, + }); + res = this.handleResponse(res); + return res as IHttpResponse; + } + /** * 初始化属性映射 * diff --git a/packages/runtime/src/controller/control/kanban/kanban.controller.ts b/packages/runtime/src/controller/control/kanban/kanban.controller.ts index ab1863630..466ae7a7b 100644 --- a/packages/runtime/src/controller/control/kanban/kanban.controller.ts +++ b/packages/runtime/src/controller/control/kanban/kanban.controller.ts @@ -3,13 +3,12 @@ /* eslint-disable no-nested-ternary */ /* eslint-disable prefer-destructuring */ import { - DataTypes, + clone, RuntimeError, isElementSame, RuntimeModelError, } from '@ibiz-template/core'; import { IDEKanban, IUIActionGroupDetail } from '@ibiz/model-core'; -import { clone, isNil } from 'ramda'; import { IController, IKanbanEvent, @@ -22,7 +21,7 @@ import { IKanbanGroupState, IToolbarController, } from '../../../interface'; -import { calcDeCodeNameById, getParentTextAppDEFieldId } from '../../../model'; +import { getParentTextAppDEFieldId } from '../../../model'; import { ControlVO } from '../../../service'; import { DataViewControlController } from '../data-view'; import { KanbanService } from './kanban.service'; @@ -104,7 +103,6 @@ export class KanbanController protected initState(): void { super.initState(); this.state.size = this.model.pagingSize || 1000; - this.state.updating = false; this.state.batching = false; this.state.selectGroupKey = ''; this.state.swimlanes = []; @@ -134,41 +132,7 @@ export class KanbanController _params?: IParams, ): Promise {} - /** - * 本地排序items - * @author lxm - * @date 2023-09-04 09:30:55 - * @param {IData[]} items - */ - sortItems(items: IData[]): void { - const { minorSortAppDEFieldId, minorSortDir } = this.model; - if (!minorSortAppDEFieldId || !minorSortDir) return; - const sortField = this.dataEntity?.appDEFields?.find( - _item => _item.codeName === minorSortAppDEFieldId, - ); - if (!sortField || !DataTypes.isNumber(sortField.stdDataType!)) { - ibiz.log.warn( - ibiz.i18n.t('runtime.controller.control.kanban.invalidSortType'), - ); - return; - } - const isAsc = minorSortDir === 'ASC'; - // 格式化排序属性的值 - items.forEach(item => { - const sortValue = item[minorSortAppDEFieldId]; - if (isNil(sortValue)) item[minorSortAppDEFieldId] = 0; - }); - // 排序 - items.sort((a, b) => - isAsc - ? a[minorSortAppDEFieldId] - b[minorSortAppDEFieldId] - : b[minorSortAppDEFieldId] - a[minorSortAppDEFieldId], - ); - } - async afterLoad(args: MDCtrlLoadParams, items: IData[]): Promise { - // 每次加载回来先本地排序,把数据的排序属性规范一下 - this.sortItems(this.state.items); super.afterLoad(args, items); this.handleLaneData(); return items; @@ -426,15 +390,16 @@ export class KanbanController * @memberof KanbanController */ async onDragChange(info: IDragChangeInfo): Promise { - if (!this.enableEditGroup) { - if (info.from !== info.to) { - ibiz.message.warning( - ibiz.i18n.t('runtime.controller.control.kanban.adjustmentsGroup'), - ); - return; - } - } const { from, to, fromIndex, toIndex, fromLane, toLane } = info; + if (!this.enableEditGroup && from !== to) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.adjustmentsGroup'), + ); + if (!this.enableEditOrder && from === to && fromLane === toLane) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.noAllowReorder'), + ); + const { groupAppDEFieldId = '', moveControlAction, @@ -443,238 +408,42 @@ export class KanbanController } = this.model; const fromGroup = this.state.groups.find(x => x.key === from)!; const toGroup = this.state.groups.find(x => x.key === to)!; - - if (!this.enableEditOrder) { - if (from === to && fromLane === toLane) { - ibiz.message.warning( - ibiz.i18n.t('runtime.controller.control.kanban.noAllowReorder'), - ); - return; - } - - // 只修改分组不管排序 - const draggedItem = fromGroup.children[fromIndex]; - // 变更分组 - draggedItem[groupAppDEFieldId] = to; - // 变更泳道 - if (swimlaneAppDEFieldId) draggedItem[swimlaneAppDEFieldId] = toLane; - return this.updateChangedItems([draggedItem] as ControlVO[]); - } - - if (!minorSortAppDEFieldId) { - throw new RuntimeModelError( - this.model, - ibiz.i18n.t('runtime.controller.control.kanban.sortingProperties'), - ); - } - - const originArr = [...toGroup.children]; - const moveAction = moveControlAction?.appDEMethodId; - - if (!moveAction) { - throw new RuntimeModelError( - this.model, - ibiz.i18n.t('runtime.controller.common.md.noMoveDataCconfig'), - ); - } - - this.state.updating = true; - // 计算移动数据,目标分组指定位置存在数据,则添加到目标分组指定位置之前,若没有,则添加到当前分组数据排序值最大的后面 - const computeMoveData = ( - _fromIndex: number, - _toIndex: number, - _draggedItem: IData, - targetArray: IData[], - isCrossGroup: boolean, - ): IData => { - let moveData = {}; - const targetItem = targetArray[_toIndex]; - if (!targetItem) { - let tempArray: IData[] = []; - if (targetArray.length > 0) { - tempArray = targetArray; - } - if (tempArray.length > 0) { - const maxItem = tempArray.reduce((prev, curr) => { - const sortCondition = - prev[minorSortAppDEFieldId] > curr[minorSortAppDEFieldId]; - if ( - sortCondition && - prev[this.dataEntity.keyAppDEFieldId!] !== _draggedItem.srfkey - ) { - return prev; - } - if ( - !sortCondition && - curr[this.dataEntity.keyAppDEFieldId!] !== _draggedItem.srfkey - ) { - return curr; - } - return prev; - }); - if ( - maxItem && - maxItem[this.dataEntity.keyAppDEFieldId!] !== _draggedItem.srfkey - ) { - moveData = { - srftargetkey: maxItem.srfkey, - srfmovetype: 'MOVEAFTER', - }; - } - } - } else { - moveData = { - srftargetkey: targetItem.srfkey, - srfmovetype: - _toIndex < targetArray.length - 1 - ? 'MOVEBEFORE' - : isCrossGroup - ? 'MOVEBEFORE' - : 'MOVEAFTER', - }; - } - return moveData; - }; - - // 拖拽数据 const draggedItem = clone(fromGroup.children[fromIndex]); + // 分组变更 + if (from !== to && groupAppDEFieldId) draggedItem[groupAppDEFieldId] = to; + // 泳道变更 + if (fromLane !== toLane && swimlaneAppDEFieldId) + draggedItem[swimlaneAppDEFieldId] = to; - // 前台先改值 - const removeItems = fromGroup.children.splice(fromIndex, 1); - toGroup.children.splice(toIndex, 0, ...removeItems); - - if (info.from !== info.to) { - // 变更分组 - draggedItem[groupAppDEFieldId] = info.to; - // 存在移动数据行为,先变更分组再变更排序 - const app = ibiz.hub.getApp(this.model.appId); - const deName = calcDeCodeNameById(this.model.appDataEntityId!); - const tempContext = this.context.clone(); - tempContext[deName] = draggedItem.srfkey; - try { - await app.deService.exec( - this.model.appDataEntityId!, - 'update', - tempContext, - draggedItem, - ); - const index = this.state.items.findIndex( - x => x.srfkey === draggedItem[this.dataEntity.keyAppDEFieldId!], + // 仅变更分组 + if (!this.enableEditOrder) { + await this.updateChangedItems([draggedItem] as ControlVO[]); + } else { + // 排序变更 + if (!minorSortAppDEFieldId) + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.controller.common.md.sortingProperties'), ); - if (index !== -1) { - this.state.items.splice(index, 1, draggedItem); - } - } catch (error) { - this.state.updating = false; + const moveAction = moveControlAction?.appDEMethodId; + if (!moveAction) throw new RuntimeModelError( this.model, - ibiz.i18n.t('runtime.controller.common.md.changeGroupError'), + ibiz.i18n.t('runtime.controller.common.md.noMoveDataCconfig'), ); + // 存在移动数据行为,先变更分组或泳道再变更排序 + if (from !== to || fromLane !== toLane) { + await this.updateChangedItems([draggedItem] as ControlVO[]); } - } - // 移动排序 - const params = computeMoveData( - fromIndex, - toIndex, - draggedItem, - originArr, - info.from !== info.to, - ); - try { - const { ok, result } = await this.moveOrderItem(draggedItem, params); - if (ok) { - // 通知实体数据变更 - this.emitDEDataChange('update', draggedItem); - // 返回空数组不做处理,非空数组同步界面数据,无数据界面重刷新 - if (Array.isArray(result) && result.length > 0) { - result.forEach(item => { - const index = this.state.items.findIndex( - x => x.srfkey === item[this.dataEntity.keyAppDEFieldId!], - ); - if (index !== -1) { - this.state.items[index][minorSortAppDEFieldId] = - item[minorSortAppDEFieldId]; - } - }); - } else { - await this.refresh(); - } - } - } catch (error) { - this.state.updating = false; - this.actionNotification(`MOVEERROR`, { - error: error as Error, - }); - } finally { - await this.afterLoad({}, this.state.items); - this.state.updating = false; - } - } - /** - * 移动并排序数据 - * - * @author tony001 - * @date 2024-06-17 15:06:22 - * @param {IData} draggedItem - * @param {IData} moveMeta - * @return {*} {Promise} - */ - async moveOrderItem( - draggedItem: IData, - moveMeta: IData, - ): Promise<{ ok: boolean; result?: ControlVO[] }> { - const deName = calcDeCodeNameById(this.model.appDataEntityId!); - const tempContext = this.context.clone(); - tempContext[deName] = draggedItem.srfkey; - if (!moveMeta.srftargetkey || !moveMeta.srfmovetype) { - ibiz.log.error( - ibiz.i18n.t('runtime.controller.common.md.computeMoveMetaError'), - ); - return { ok: false }; - } - const res = await this.service.moveOrderItem( - tempContext, - draggedItem as ControlVO, - moveMeta, - ); - return { ok: true, result: res.data }; - } - - /** - * 批量更新修改的项,并更新后台返回的数据,然后重新计算分组和排序 - * @author lxm - * @date 2023-09-11 04:13:15 - * @param {ControlVO[]} changedItems - * @return {*} {Promise} - */ - async updateChangedItems(changedItems: ControlVO[]): Promise { - try { - this.state.updating = true; - await Promise.all( - changedItems.map(async item => { - // 往上下文添加主键 - const deName = calcDeCodeNameById(this.model.appDataEntityId!); - const tempContext = this.context.clone(); - tempContext[deName] = item.srfkey; - - // 调用接口修改数据 - const res = await this.service.updateGroup(tempContext, item); - - // 更新完之后更新state里的数据。 - if (res.ok) { - // 通知实体数据变更 - this.emitDEDataChange('update', res.data); - const index = this.state.items.findIndex( - x => x.srfkey === item.srfkey, - ); - this.state.items.splice(index, 1, res.data); - } - }), + const params = this.computeMoveDataParam( + fromIndex, + toIndex, + draggedItem, + toGroup.children, + info.from !== info.to, ); - } finally { - this.state.updating = false; - await this.afterLoad({}, this.state.items); + await this.moveOrderItem(draggedItem as ControlVO, params); } } diff --git a/packages/runtime/src/controller/control/kanban/kanban.service.ts b/packages/runtime/src/controller/control/kanban/kanban.service.ts index 0f3541821..321b7037d 100644 --- a/packages/runtime/src/controller/control/kanban/kanban.service.ts +++ b/packages/runtime/src/controller/control/kanban/kanban.service.ts @@ -1,7 +1,4 @@ -import { IHttpResponse } from '@ibiz-template/core'; import { IDEKanban } from '@ibiz/model-core'; -import { clone } from 'ramda'; -import { ControlVO } from '../../../service'; import { DataViewControlService } from '../data-view'; /** @@ -11,48 +8,4 @@ import { DataViewControlService } from '../data-view'; * @class DataViewControlService * @extends {MDControlService} */ -export class KanbanService extends DataViewControlService { - /** - * 更新分组数据 - * - * @author lxm - * @date 2022-09-07 19:09:11 - * @param {IContext} context 上下文 - * @param {ControlVO} data 数据 - * @returns {*} - */ - async updateGroup( - context: IContext, - data: ControlVO, - ): Promise> { - const updateAction = - this.model.updateGroupControlAction?.appDEMethodId || 'update'; - let res = await this.exec(updateAction, context, data.getOrigin()); - res = this.handleResponse(res); - return res as IHttpResponse; - } - - /** - * 移动并排序数据 - * - * @author tony001 - * @date 2024-06-16 12:06:02 - * @param {IContext} context - * @param {ControlVO} data - * @return {*} {Promise>} - */ - async moveOrderItem( - context: IContext, - data: ControlVO, - args: IData, - ): Promise> { - const moveAction = this.model.moveControlAction!.appDEMethodId!; - const params = clone(data.getOrigin()); - Object.assign(params, args); - let res = await this.exec(moveAction, context, params, { - srfupdateitem: true, - }); - res = this.handleResponse(res); - return res as IHttpResponse; - } -} +export class KanbanService extends DataViewControlService {} diff --git a/packages/runtime/src/interface/api/state/control/i-api-data-view-control.state.ts b/packages/runtime/src/interface/api/state/control/i-api-data-view-control.state.ts index 198477772..c99ee2ef1 100644 --- a/packages/runtime/src/interface/api/state/control/i-api-data-view-control.state.ts +++ b/packages/runtime/src/interface/api/state/control/i-api-data-view-control.state.ts @@ -9,6 +9,14 @@ import { IApiMDControlState } from './i-api-md-control.state'; * @extends {IApiMDControlState} */ export interface IApiDataViewControlState extends IApiMDControlState { + /** + * @description 是否正在更新 + * @type {boolean} + * @default false + * @memberof IApiDataViewControlState + */ + updating: boolean; + /** * @description 是否可拖拽 * @type {boolean} @@ -16,6 +24,7 @@ export interface IApiDataViewControlState extends IApiMDControlState { * @memberof IApiKanbanState */ draggable: boolean; + /** * @description 是否只读 * @type {boolean} @@ -23,6 +32,7 @@ export interface IApiDataViewControlState extends IApiMDControlState { * @memberof IApiDataViewControlState */ readonly: boolean; + /** * @description 排序项集合 * @type {IApiSortItem[]} diff --git a/packages/runtime/src/interface/api/state/control/i-api-kanban.state.ts b/packages/runtime/src/interface/api/state/control/i-api-kanban.state.ts index f365ce5d5..7625423de 100644 --- a/packages/runtime/src/interface/api/state/control/i-api-kanban.state.ts +++ b/packages/runtime/src/interface/api/state/control/i-api-kanban.state.ts @@ -10,14 +10,6 @@ import { IApiMDControlGroupState } from './i-api-md-control.state'; * @extends {IApiDataViewControlState} */ export interface IApiKanbanState extends IApiDataViewControlState { - /** - * @description 是否正在更新 - * @type {boolean} - * @default false - * @memberof IApiKanbanState - */ - updating: boolean; - /** * @description 是否正在批操作 * @type {boolean} diff --git a/packages/runtime/src/locale/en/index.ts b/packages/runtime/src/locale/en/index.ts index ca50a380e..6caa789cc 100644 --- a/packages/runtime/src/locale/en/index.ts +++ b/packages/runtime/src/locale/en/index.ts @@ -43,6 +43,11 @@ export const en = { 'Error in calculating target position and movement type', unclassified: 'Unclassified', today: 'Today', + adjustmentsGroup: + 'The current control does not allow adjustments to the grouping!', + noAllowReorder: 'Current control does not allow reordering!', + sortingProperties: 'Sorting attribute not configured', + invalidSortType: 'Sorting property is not a numeric type', }, editor: { editorNoConfigured: @@ -175,13 +180,8 @@ export const en = { 'No interface behavior is configured for the action column interface behavior group', }, kanban: { - sortingProperties: 'Sorting properties are not configured', sortDirection: 'Sort direction is not configured', groupedOn: 'Kanban components must be grouped on', - adjustmentsGroup: - 'The current Kanban does not allow adjustments to the grouping!', - noAllowReorder: 'Current Kanban does not allow reordering!', - invalidSortType: 'Sorting property is not a numeric type', }, meditViewPanel: { DraftNew: 'Draft - New', diff --git a/packages/runtime/src/locale/zh-CN/index.ts b/packages/runtime/src/locale/zh-CN/index.ts index 1ab274d2b..fa0f7bfea 100644 --- a/packages/runtime/src/locale/zh-CN/index.ts +++ b/packages/runtime/src/locale/zh-CN/index.ts @@ -38,6 +38,10 @@ export const zhCn = { computeMoveMetaError: '计算目标位置和移动类型发生错误', unclassified: '未分类', today: '今天', + adjustmentsGroup: '当前部件不允许调整分组!', + noAllowReorder: '当前部件不允许调整次序!', + sortingProperties: '未配置排序属性', + invalidSortType: '排序属性不是数值类型', }, editor: { editorNoConfigured: '编辑器类型[{editorType}],未配置代码表', @@ -142,12 +146,8 @@ export const zhCn = { interfaceBehavior: '操作列界面行为组没有配置界面行为', }, kanban: { - sortingProperties: '排序属性没配置', sortDirection: '排序方向没配置', groupedOn: '看板部件必须开启分组', - adjustmentsGroup: '当前看板不允许调整分组!', - noAllowReorder: '当前看板不允许调整次序!', - invalidSortType: '排序属性不是数值类型', }, meditViewPanel: { DraftNew: '草稿--新建', -- Gitee From 9fa27dd0dffd0566f263c8b1502cbe567c0537cd Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Thu, 31 Jul 2025 15:59:11 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=96=B0=E5=BB=BA=E5=92=8C=E6=8B=96=E6=8B=BD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/CHANGELOG.md | 1 + .../control/list/list.controller.ts | 321 +++++++++++++++++- .../controller/control/list/list.service.ts | 26 +- .../api/state/control/i-api-list.state.ts | 24 ++ 4 files changed, 362 insertions(+), 10 deletions(-) diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index f67c3ac67..33702c1c5 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -10,6 +10,7 @@ ### Added - 新增卡片拖拽功能 +- 新增列表新建和拖拽功能 ## [0.7.41-alpha.16] - 2025-07-30 diff --git a/packages/runtime/src/controller/control/list/list.controller.ts b/packages/runtime/src/controller/control/list/list.controller.ts index 9f922297d..d3ad66cfa 100644 --- a/packages/runtime/src/controller/control/list/list.controller.ts +++ b/packages/runtime/src/controller/control/list/list.controller.ts @@ -1,13 +1,20 @@ +/* eslint-disable no-nested-ternary */ import { IDEList, IUIActionGroupDetail } from '@ibiz/model-core'; import { createUUID, isBoolean } from 'qx-util'; -import { clone, isNil } from 'ramda'; -import { isElementSame } from '@ibiz-template/core'; +import { isNil } from 'ramda'; +import { + clone, + DataTypes, + isElementSame, + RuntimeModelError, +} from '@ibiz-template/core'; import dayjs from 'dayjs'; import { IListState, IListEvent, CodeListItem, IListController, + IDragChangeInfo, MDCtrlLoadParams, IApiMDGroupParams, IMDControlGroupState, @@ -16,11 +23,12 @@ import { MDControlController } from '../../common'; import { ListService } from './list.service'; import { formatDate, - ButtonContainerState, UIActionButtonState, + ButtonContainerState, } from '../../utils'; import { UIActionUtil } from '../../../ui-action'; -import { getParentTextAppDEFieldId } from '../../../model'; +import { calcDeCodeNameById, getParentTextAppDEFieldId } from '../../../model'; +import { ControlVO } from '../../../service'; export class ListController extends MDControlController @@ -28,6 +36,36 @@ export class ListController { declare service: ListService; + /** + * @description 是否允许新建 + * @readonly + * @type {boolean} + * @memberof ListController + */ + get enableNew(): boolean { + return this.model.enableRowNew === true; + } + + /** + * @description 是否允许调整顺序 + * @readonly + * @type {boolean} + * @memberof ListController + */ + get enableEditOrder(): boolean { + return this.model.enableRowEditOrder === true; + } + + /** + * @description 是否支持调整分组 + * @readonly + * @type {boolean} + * @memberof ListController + */ + get enableEditGroup(): boolean { + return this.model.enableRowEditGroup === true; + } + protected initState(): void { super.initState(); this.state.noSort = this.model.noSort === true; @@ -36,6 +74,10 @@ export class ListController const { enablePagingBar } = this.model; this.state.enablePagingBar = enablePagingBar; this.state.uaState = {}; + this.state.readonly = !!( + this.context.srfreadonly === true || this.context.srfreadonly === 'true' + ); + this.state.draggable = this.enableEditOrder || this.enableEditGroup; } protected async onCreated(): Promise { @@ -184,6 +226,8 @@ export class ListController async afterLoad(args: MDCtrlLoadParams, items: IData[]): Promise { super.afterLoad(args, items); + // 每次加载回来先本地排序,把数据的排序属性规范一下 + this.sortItems(this.state.items); await this.handleDataGroup(); await this.initGroupActionStates(); await this.calcOptItemState(items); @@ -194,7 +238,7 @@ export class ListController /** * @description 获取操作项行为集合模型 * @returns {*} {IUIActionGroupDetail[]} - * @memberof DataViewControlController + * @memberof ListController */ getOptItemModel(): IUIActionGroupDetail[] { const actions: IUIActionGroupDetail[] = []; @@ -215,7 +259,7 @@ export class ListController * @description 计算操作项状态 * @param {IData[]} items * @returns {*} {Promise} - * @memberof DataViewControlController + * @memberof ListController */ async calcOptItemState(items: IData[]): Promise { const actions = this.getOptItemModel(); @@ -331,7 +375,7 @@ export class ListController /** * 处理数据分组 * - * @memberof DataViewControlController + * @memberof ListController */ protected async handleDataGroup(): Promise { const { groupMode } = this.model; @@ -351,7 +395,7 @@ export class ListController /** * 处理自动分组 * - * @memberof DataViewControlController + * @memberof ListController */ protected async handleAutoGroup(): Promise { const { groupAppDEFieldId, groupCodeListId } = this.model; @@ -417,7 +461,7 @@ export class ListController /** * 处理代码表分组 * - * @memberof DataViewControlController + * @memberof ListController */ protected async handleCodeListGroup(): Promise { const { groupAppDEFieldId, groupCodeListId } = this.model; @@ -478,4 +522,263 @@ export class ListController this.state.expandedKeys = []; } } + + /** + * @description 点击新建 + * @param {MouseEvent} event + * @param {(string | number)} [group] + * @memberof ListController + */ + onClickNew(event: MouseEvent, group?: string | number): void { + const params = { ...this.params }; + if (group) Object.assign(params, { srfgroup: group }); + UIActionUtil.execAndResolved( + 'new', + { + context: this.context, + params, + data: [], + view: this.view, + ctrl: this, + event, + }, + this.view.model.appId, + ); + } + + /** + * @description 本地排序items + * @param {IData[]} items + * @returns {*} {void} + * @memberof ListController + */ + sortItems(items: IData[]): void { + const { minorSortAppDEFieldId, minorSortDir } = this.model; + if (!minorSortAppDEFieldId || !minorSortDir) return; + const sortField = this.dataEntity?.appDEFields?.find( + _item => _item.codeName === minorSortAppDEFieldId, + ); + if (!sortField || !DataTypes.isNumber(sortField.stdDataType!)) { + ibiz.log.warn( + ibiz.i18n.t('runtime.controller.common.md.invalidSortType'), + ); + return; + } + const isAsc = minorSortDir === 'ASC'; + // 格式化排序属性的值 + items.forEach(item => { + const sortValue = item[minorSortAppDEFieldId]; + if (isNil(sortValue)) item[minorSortAppDEFieldId] = 0; + }); + // 排序 + items.sort((a, b) => + isAsc + ? a[minorSortAppDEFieldId] - b[minorSortAppDEFieldId] + : b[minorSortAppDEFieldId] - a[minorSortAppDEFieldId], + ); + } + + /** + * @description 计算移动数据参数 + * @protected + * @param {number} fromIndex 变更前的索引位置 + * @param {number} toIndex 变更后的索引位置 + * @param {IData} draggedItem 拖拽数据项 + * @param {IData[]} targetArray 数据集 + * @param {boolean} isCrossGroup 是否切换分组 + * @returns {*} {IData} + * @memberof ListController + */ + protected computeMoveDataParam( + fromIndex: number, + toIndex: number, + draggedItem: IData, + targetArray: IData[], + isCrossGroup: boolean, + ): IData { + let moveData = {}; + const { minorSortAppDEFieldId } = this.model; + if (!minorSortAppDEFieldId) return moveData; + const targetItem = targetArray[toIndex]; + if (!targetItem) { + let tempArray: IData[] = []; + if (targetArray.length > 0) { + tempArray = targetArray; + } + if (tempArray.length > 0) { + const maxItem = tempArray.reduce((prev, curr) => { + const sortCondition = + prev[minorSortAppDEFieldId] > curr[minorSortAppDEFieldId]; + if ( + sortCondition && + prev[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + return prev; + } + if ( + !sortCondition && + curr[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + return curr; + } + return prev; + }); + if ( + maxItem && + maxItem[this.dataEntity.keyAppDEFieldId!] !== draggedItem.srfkey + ) { + moveData = { + srftargetkey: maxItem.srfkey, + srfmovetype: 'MOVEAFTER', + }; + } + } + } else { + moveData = { + srftargetkey: targetItem.srfkey, + srfmovetype: + toIndex < targetArray.length - 1 + ? 'MOVEBEFORE' + : isCrossGroup + ? 'MOVEBEFORE' + : 'MOVEAFTER', + }; + } + return moveData; + } + + /** + * @description 移动并排序数据 + * @param {ControlVO} draggedItem + * @param {IData} moveMeta + * @returns {*} {Promise} + * @memberof ListController + */ + async moveOrderItem(draggedItem: ControlVO, moveMeta: IData): Promise { + try { + this.state.updating = true; + const { minorSortAppDEFieldId } = this.model; + if (!minorSortAppDEFieldId) + return ibiz.log.error( + ibiz.i18n.t('runtime.controller.common.md.sortingProperties'), + ); + const deName = calcDeCodeNameById(this.model.appDataEntityId!); + const tempContext = this.context.clone(); + tempContext[deName] = draggedItem.srfkey; + if (!moveMeta.srftargetkey || !moveMeta.srfmovetype) + return ibiz.log.error( + ibiz.i18n.t('runtime.controller.common.md.computeMoveMetaError'), + ); + const res = await this.service.moveOrderItem( + tempContext, + draggedItem, + moveMeta, + ); + if (res.ok) { + // 通知实体数据变更 + this.emitDEDataChange('update', res.data); + res.data.forEach(_item => { + const item = this.state.items.find(x => x.srfkey === _item.srfkey); + if (item) item[minorSortAppDEFieldId] = _item[minorSortAppDEFieldId]; + }); + await this.afterLoad({}, this.state.items); + } + } finally { + this.state.updating = false; + } + } + + /** + * @description 批量更新修改项 + * @param {ControlVO[]} changedItems + * @returns {*} {Promise} + * @memberof ListController + */ + async updateChangedItems(changedItems: ControlVO[]): Promise { + try { + this.state.updating = true; + await Promise.all( + changedItems.map(async item => { + // 往上下文添加主键 + const deName = calcDeCodeNameById(this.model.appDataEntityId!); + const tempContext = this.context.clone(); + tempContext[deName] = item.srfkey; + // 调用接口修改数据 + const res = await this.service.update(tempContext, item); + // 更新完之后更新state里的数据。 + if (res.ok) { + // 通知实体数据变更 + this.emitDEDataChange('update', res.data); + const index = this.state.items.findIndex( + x => x.srfkey === item.srfkey, + ); + this.state.items.splice(index, 1, res.data); + } + }), + ); + } finally { + this.state.updating = false; + await this.afterLoad({}, this.state.items); + } + } + + /** + * @description 拖拽变更 + * @param {IDragChangeInfo} info + * @returns {*} {Promise} + * @memberof ListController + */ + async onDragChange(info: IDragChangeInfo): Promise { + const { from, to, fromIndex, toIndex } = info; + if (!this.enableEditGroup && from !== to) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.adjustmentsGroup'), + ); + if (!this.enableEditOrder && from === to) + return ibiz.message.warning( + ibiz.i18n.t('runtime.controller.common.md.noAllowReorder'), + ); + + const { groupAppDEFieldId, moveControlAction, minorSortAppDEFieldId } = + this.model; + const fromGroup = this.state.groups.find(x => x.key === from); + const toGroup = this.state.groups.find(x => x.key === to); + const draggedItem = clone( + fromGroup?.children[fromIndex] || this.state.items[fromIndex], + ); + + // 分组变更 + if (from !== to && groupAppDEFieldId) draggedItem[groupAppDEFieldId] = to; + + // 仅变更分组 + if (!this.enableEditOrder) { + await this.updateChangedItems([draggedItem] as ControlVO[]); + } else { + // 排序变更 + if (!minorSortAppDEFieldId) + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.controller.common.md.sortingProperties'), + ); + const moveAction = moveControlAction?.appDEMethodId; + if (!moveAction) + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.controller.common.md.noMoveDataCconfig'), + ); + // 存在移动数据行为,先变更分组再变更排序 + if (from !== to) { + await this.updateChangedItems([draggedItem] as ControlVO[]); + } + const originArr = toGroup?.children || this.state.items; + const params = this.computeMoveDataParam( + fromIndex, + toIndex, + draggedItem, + originArr, + info.from !== info.to, + ); + await this.moveOrderItem(draggedItem as ControlVO, params); + } + } } diff --git a/packages/runtime/src/controller/control/list/list.service.ts b/packages/runtime/src/controller/control/list/list.service.ts index 2f512b22a..b88b32b65 100644 --- a/packages/runtime/src/controller/control/list/list.service.ts +++ b/packages/runtime/src/controller/control/list/list.service.ts @@ -1,5 +1,6 @@ import { IDEList } from '@ibiz/model-core'; -import { MDControlService, UIMapField } from '../../../service'; +import { IHttpResponse, clone } from '@ibiz-template/core'; +import { ControlVO, MDControlService, UIMapField } from '../../../service'; /** * 列表部件服务 @@ -10,6 +11,29 @@ import { MDControlService, UIMapField } from '../../../service'; * @extends {MDControlService} */ export class ListService extends MDControlService { + /** + * @description 移动并排序数据 + * @param {IContext} context + * @param {ControlVO} data + * @param {IData} args + * @returns {*} {Promise>} + * @memberof ListService + */ + async moveOrderItem( + context: IContext, + data: ControlVO, + args: IData, + ): Promise> { + const moveAction = this.model.moveControlAction!.appDEMethodId!; + const params = clone(data.getOrigin()); + Object.assign(params, args); + let res = await this.exec(moveAction, context, params, { + srfupdateitem: true, + }); + res = this.handleResponse(res); + return res as IHttpResponse; + } + /** * 初始化属性映射 * diff --git a/packages/runtime/src/interface/api/state/control/i-api-list.state.ts b/packages/runtime/src/interface/api/state/control/i-api-list.state.ts index 995d78841..314a06186 100644 --- a/packages/runtime/src/interface/api/state/control/i-api-list.state.ts +++ b/packages/runtime/src/interface/api/state/control/i-api-list.state.ts @@ -9,6 +9,30 @@ import { IButtonContainerState } from '../../../controller'; * @extends {IApiMDControlState} */ export interface IApiListState extends IApiMDControlState { + /** + * @description 是否正在更新 + * @type {boolean} + * @default false + * @memberof IApiListState + */ + updating: boolean; + + /** + * @description 是否可拖拽 + * @type {boolean} + * @default false + * @memberof IApiListState + */ + draggable: boolean; + + /** + * @description 是否只读 + * @type {boolean} + * @default false + * @memberof IApiListState + */ + readonly: boolean; + /** * @description 是否显示分页栏 * @type {boolean} -- Gitee From 6cfb237baadc2746946d19861f28610b413d2f9e Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Thu, 31 Jul 2025 18:32:30 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/control/i-api-data-view-control.controller.ts | 1 + .../api/controller/control/i-api-kanban.controller.ts | 3 +++ .../interface/api/controller/control/i-api-list.controller.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/packages/runtime/src/interface/api/controller/control/i-api-data-view-control.controller.ts b/packages/runtime/src/interface/api/controller/control/i-api-data-view-control.controller.ts index b0a1e992a..ef2fabb45 100644 --- a/packages/runtime/src/interface/api/controller/control/i-api-data-view-control.controller.ts +++ b/packages/runtime/src/interface/api/controller/control/i-api-data-view-control.controller.ts @@ -11,6 +11,7 @@ import { IApiMDControlController } from './i-api-md-control.controller'; * @extends {IApiMDControlController} * @ctrlparams {"name":"cardstyle","title":"卡片样式","defaultvalue":"'default'","parameterType":"'default' | 'style2' | 'userstyle'","description":"当该值为'style2' 且启用多选功能时,卡片中将显示复选框用于多选;当值为'userstyle'时,视图上的搜索栏将绘制在数据视图部件中","effectPlatform":"web"} * @ctrlparams {"name":"showmode","title":"显示模式","defaultvalue":"'DEFAULT'","parameterType":"'DEFAULT' | 'ONLYDATA' | 'MIXIN'","description":"'DEFAULT' 显示分页栏和无数据提示的文字及图片;'ONLYDATA' 仅显示数据区域,分页栏不显示,在无值时不显示无数据提示图片;'MIXIN' 无值时仅显示数据区域,不显示分页栏和无数据提示图片","effectPlatform":"web"} + * @ctrlparams {"name":"mdctrlrefreshmode","title":"刷新模式","defaultvalue":"'cache'","parameterType":"'nocache' | 'cache'","description":"多数据部件刷新模式,当值为 'cache',部件刷新时保留选中数据;当值为 'nocache',部件刷新时清空选中数据","effectPlatform":"web"} * @template T * @template S */ diff --git a/packages/runtime/src/interface/api/controller/control/i-api-kanban.controller.ts b/packages/runtime/src/interface/api/controller/control/i-api-kanban.controller.ts index 86fe28eae..ea29516ee 100644 --- a/packages/runtime/src/interface/api/controller/control/i-api-kanban.controller.ts +++ b/packages/runtime/src/interface/api/controller/control/i-api-kanban.controller.ts @@ -12,6 +12,9 @@ import { IApiDataViewControlController } from './i-api-data-view-control.control * @extends {IApiDataViewControlController} * @ctrlparams {"name":"draggablemode","title":"拖拽模式","defaultvalue": "3","parameterType":"0 | 1 | 2 | 3","description":"该参数控制看板的拖拽能力,可选值为:0:无拖拽, 1:仅同分组,2:仅同泳道,3:全部","effectPlatform":"web"} * @ctrlparams {"name":"lanedescription","title":"泳道描述","defaultvalue": "","parameterType":"string","description":"该参数用于显示泳道的描述信息,默认为实体逻辑名称","effectPlatform":"web"} + * @ctrlparams {"name":"mdctrlrefreshmode","title":"刷新模式","defaultvalue":"'cache'","parameterType":"'nocache' | 'cache'","description":"多数据部件刷新模式,当值为 'cache',部件刷新时保留选中数据;当值为 'nocache',部件刷新时清空选中数据","effectPlatform":"web"} + * @ctrlparams {"name":"enablefullscreen","title":"是否启用全屏功能","defaultvalue": "true","parameterType":"boolean","description":"该参数用于设置看板是否启用全屏功能","effectPlatform":"web"} + * @ctrlparams {"name":"enablegrouphidden","title":"是否启用隐藏分组功能","defaultvalue": "false","parameterType":"boolean","description":"该参数用于设置看板是否启用隐藏分组功能","effectPlatform":"web"} * @template T * @template S */ diff --git a/packages/runtime/src/interface/api/controller/control/i-api-list.controller.ts b/packages/runtime/src/interface/api/controller/control/i-api-list.controller.ts index 217a22ce6..b8258f2ad 100644 --- a/packages/runtime/src/interface/api/controller/control/i-api-list.controller.ts +++ b/packages/runtime/src/interface/api/controller/control/i-api-list.controller.ts @@ -12,6 +12,7 @@ import { IApiMDControlController } from './i-api-md-control.controller'; * @extends {IApiMDControlController} * @ctrlparams {"name":"showmode","title":"显示模式","defaultvalue":"'DEFAULT'","parameterType":"'DEFAULT' | 'ONLYDATA' | 'MIXIN'","description":"'DEFAULT' 显示分页栏和无数据提示的文字及图片;'ONLYDATA' 仅显示数据区域,分页栏不显示,在无值时不显示无数据提示图片;'MIXIN' 无值时仅显示数据区域,不显示分页栏和无数据提示图片"} * @ctrlparams {name:defaultexpandall,title:默认展开所有,parameterType:boolean,defaultvalue:false,description:列表默认是否将所有分组展开显示} + * @ctrlparams {"name":"mdctrlrefreshmode","title":"刷新模式","defaultvalue":"'cache'","parameterType":"'nocache' | 'cache'","description":"多数据部件刷新模式,当值为 'cache',部件刷新时保留选中数据;当值为 'nocache',部件刷新时清空选中数据","effectPlatform":"web"} * @template T * @template S */ -- Gitee From 386ff0695d242a545cf356191b0b6d1334225b26 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Thu, 31 Jul 2025 19:57:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controller/control/grid/grid/grid.controller.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/controller/control/grid/grid/grid.controller.ts b/packages/runtime/src/controller/control/grid/grid/grid.controller.ts index 5c766c3a4..e95aee1c3 100644 --- a/packages/runtime/src/controller/control/grid/grid/grid.controller.ts +++ b/packages/runtime/src/controller/control/grid/grid/grid.controller.ts @@ -796,7 +796,10 @@ export class GridController< item => item.value === key, ); this.state.groups.push({ - caption: codeListItem?.text || `${key}`, + caption: + codeListItem?.text || + (key as string) || + ibiz.i18n.t('runtime.controller.common.md.unclassified'), key, children: value, }); -- Gitee