From 2e6cf0dc766e2760fd689e6da29e47ee64d4d96f Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 18:35:31 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E5=88=86=E9=A1=B5=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + src/control/kanban/kanban.scss | 12 +++ src/control/kanban/kanban.tsx | 99 +++++++++++-------- .../swimlane-kanban/swimlane-kanban.scss | 1 + .../swimlane-kanban/swimlane-kanban.tsx | 64 ++++++++++-- 5 files changed, 134 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d7d8c9..853aacdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - 修复分页搜索视图引擎设置搜索栏placeholder时因为无搜索栏而报错的问题 +### Added + +- 新增看板分页栏 + ## [0.7.41-alpha.16] - 2025-07-30 ### Added diff --git a/src/control/kanban/kanban.scss b/src/control/kanban/kanban.scss index 08cf95fb..5c372a2e 100644 --- a/src/control/kanban/kanban.scss +++ b/src/control/kanban/kanban.scss @@ -11,9 +11,21 @@ $control-kanban: ( @include set-component-css-var(control-kanban, $control-kanban); display: flex; + flex-direction: column; width: 100%; height: 100%; + @include when(enable-page) { + .#{bem(control-kanban, content)} { + height: calc(100% - 50px); + } + } + + @include e(content) { + display: flex; + flex-grow: 1; + } + @include m(row) { @include b(control-kanban-group-container) { @include flex(row); diff --git a/src/control/kanban/kanban.tsx b/src/control/kanban/kanban.tsx index 39947e8c..0995ad9f 100644 --- a/src/control/kanban/kanban.tsx +++ b/src/control/kanban/kanban.tsx @@ -32,6 +32,7 @@ import { } from '@ibiz-template/runtime'; import { NOOP, listenJSEvent } from '@ibiz-template/core'; import { SwimlaneKanban } from './swimlane-kanban/swimlane-kanban'; +import { usePagination } from '../../util'; import './kanban.scss'; export const KanbanControl = defineComponent({ @@ -84,9 +85,8 @@ export const KanbanControl = defineComponent({ const ns = useNamespace(`control-${c.model.controlType!.toLowerCase()}`); const kanban = ref(); const isFull: Ref = ref(false); - const disabled = computed(() => { - return !c.state.draggable || c.state.updating; - }); + + const { onPageChange, onPageRefresh, onPageSizeChange } = usePagination(c); // 本地数据模式 const initSimpleData = (): void => { @@ -560,7 +560,9 @@ export const KanbanControl = defineComponent({ modelValue={group.children} group={c.model.id} itemKey='srfkey' - disabled={disabled.value || c.state.readonly} + disabled={ + !c.state.draggable || c.state.updating || c.state.readonly + } onChange={(evt: IData) => onChange(evt, group.key)} > {{ @@ -611,8 +613,11 @@ export const KanbanControl = defineComponent({ ns, isFull, kanban, - onFullScreen, renderGroup, + onFullScreen, + onPageChange, + onPageRefresh, + onPageSizeChange, }; }, render() { @@ -627,42 +632,58 @@ export const KanbanControl = defineComponent({ this.ns.m(this.modelData.groupLayout?.toLowerCase()), this.ns.is('full', this.isFull), this.ns.is('swimlane', !!swimlaneAppDEFieldId), + this.ns.is('enable-page', this.c.state.enablePagingBar), ]} > - {swimlaneAppDEFieldId ? ( - - ) : ( - [ -
- {groups.length > 0 && - groups.map(group => { - if (group.hidden) return null; - return this.renderGroup(group); - })} -
, - groups.length > 0 && ( -
- {this.c.enableGroupHidden && ( - - )} - {this.c.enableFullScreen && ( - - - - )} -
- ), - ] +
+ {swimlaneAppDEFieldId ? ( + + ) : ( + [ +
+ {groups.length > 0 && + groups.map(group => { + if (group.hidden) return null; + return this.renderGroup(group); + })} +
, + groups.length > 0 && ( +
+ {this.c.enableGroupHidden && ( + + )} + {this.c.enableFullScreen && ( + + + + )} +
+ ), + ] + )} +
+ {this.c.state.enablePagingBar && ( + )} ); diff --git a/src/control/kanban/swimlane-kanban/swimlane-kanban.scss b/src/control/kanban/swimlane-kanban/swimlane-kanban.scss index a18cb279..9b17dc39 100644 --- a/src/control/kanban/swimlane-kanban/swimlane-kanban.scss +++ b/src/control/kanban/swimlane-kanban/swimlane-kanban.scss @@ -10,6 +10,7 @@ $swimlane-kanban: ( width: 100%; height: 100%; padding: getCssVar('spacing', 'tight'); + background-color: getCssVar('color', 'white'); @include e('default') { .#{bem('swimlane-kanban', 'cell')}:not(.is-collapsed) { diff --git a/src/control/kanban/swimlane-kanban/swimlane-kanban.tsx b/src/control/kanban/swimlane-kanban/swimlane-kanban.tsx index 09a2378d..ecc9b324 100644 --- a/src/control/kanban/swimlane-kanban/swimlane-kanban.tsx +++ b/src/control/kanban/swimlane-kanban/swimlane-kanban.tsx @@ -1,5 +1,13 @@ /* eslint-disable no-nested-ternary */ -import { defineComponent, PropType, ref, computed } from 'vue'; +import { + ref, + Ref, + PropType, + computed, + onMounted, + defineComponent, + onBeforeUnmount, +} from 'vue'; import { useUIStore, useNamespace } from '@ibiz-template/vue3-util'; import { IUIActionGroupDetail } from '@ibiz/model-core'; import { @@ -9,7 +17,7 @@ import { IKanbanGroupState, } from '@ibiz-template/runtime'; import draggable from 'vuedraggable'; -import { showTitle } from '@ibiz-template/core'; +import { NOOP, listenJSEvent, showTitle } from '@ibiz-template/core'; import './swimlane-kanban.scss'; /** @@ -30,6 +38,7 @@ export const SwimlaneKanban = defineComponent({ const ns = useNamespace('swimlane-kanban'); const c = props.controller; const { zIndex } = useUIStore(); + const isFull: Ref = ref(false); /** * popper样式 */ @@ -45,6 +54,20 @@ export const SwimlaneKanban = defineComponent({ */ const dropdownKey = ref(); + const swimlaneKanban = ref(); + + let cleanup = NOOP; + + onMounted(() => { + cleanup = listenJSEvent(window, 'resize', () => { + isFull.value = c.getFullscreen(); + }); + }); + + onBeforeUnmount(() => { + if (cleanup !== NOOP) cleanup(); + }); + /** * 是否禁止拖拽 */ @@ -94,6 +117,14 @@ export const SwimlaneKanban = defineComponent({ */ let cacheInfo: Partial | null = null; + /** + * @description 全屏 + */ + const onFullScreen = () => { + const container = swimlaneKanban.value; + isFull.value = c.onFullScreen(container); + }; + /** * @description 拖拽改变 * @param {IData} evt @@ -364,14 +395,32 @@ export const SwimlaneKanban = defineComponent({ {ibiz.i18n.t('control.kanban.lane')} - {c.enableGroupHidden && ( -
+
+ {c.enableGroupHidden && ( -
- )} + )} + {c.enableFullScreen && ( + + + + )} +
{c.state.groups.map(group => { @@ -652,11 +701,12 @@ export const SwimlaneKanban = defineComponent({ ); }; - return { ns, width, renderHeader, renderBody }; + return { ns, swimlaneKanban, width, renderHeader, renderBody }; }, render() { return (
Date: Fri, 1 Aug 2025 18:36:03 +0800 Subject: [PATCH 2/8] =?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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/control/data-view/data-view.tsx | 118 +++++++++++++++++++++------- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 853aacdc..d06d8a2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Added - 新增看板分页栏 +- 新增卡片拖拽 ## [0.7.41-alpha.16] - 2025-07-30 diff --git a/src/control/data-view/data-view.tsx b/src/control/data-view/data-view.tsx index 00eb9bb5..f31e59e7 100644 --- a/src/control/data-view/data-view.tsx +++ b/src/control/data-view/data-view.tsx @@ -27,13 +27,18 @@ import { IControlProvider, IMDControlGroupState, DataViewControlController, + IDragChangeInfo, } from '@ibiz-template/runtime'; import { createUUID } from 'qx-util'; +import draggable from 'vuedraggable'; import { usePagination } from '../../util'; import './data-view.scss'; export const DataViewControl = defineComponent({ name: 'IBizDataViewControl', + components: { + draggable, + }, props: { /** * @description 数据视图(卡片)模型数据 @@ -220,6 +225,33 @@ export const DataViewControl = defineComponent({ return c.onDbRowClick(item); }; + let cacheInfo: Partial | null = null; + const onDraggableChange = (evt: IData, groupKey?: string | number) => { + if (evt.moved) { + // 排序 + c.onDragChange({ + from: groupKey!, + to: groupKey!, + fromIndex: evt.moved.oldIndex, + toIndex: evt.moved.newIndex, + }); + } + if (evt.added) { + cacheInfo = { + to: groupKey, + toIndex: evt.added.newIndex, + }; + } + if (evt.removed) { + if (cacheInfo) { + cacheInfo.from = groupKey; + cacheInfo.fromIndex = evt.removed.oldIndex; + c.onDragChange(cacheInfo as IDragChangeInfo); + } + cacheInfo = null; + } + }; + /** * @description 绘制新建卡片项 * @param {IMDControlGroupState} [group] @@ -348,13 +380,24 @@ export const DataViewControl = defineComponent({ * @param {IData[]} items * @return {*} */ - const renderCardLayout = (items: IData[], group?: IMDControlGroupState) => { + const renderCardLayout = ( + items: IData[], + group?: IMDControlGroupState, + disabled: boolean = true, + ) => { const { cardColXS, cardColSM, cardColMD, cardColLG } = c.model; if (cardColXS || cardColSM || cardColMD || cardColLG) return ( - - {items.map(item => { - return ( + onDraggableChange(evt, group?.key)} + > + {{ + item: ({ element }: { element: IData }) => ( -
{renderCard(item)}
+
{renderCard(element)}
- ); - })} - {c.enableNew && !c.state.readonly && ( - -
{renderNewCard(group)}
-
- )} -
+ ), + footer: () => { + if (c.enableNew && !c.state.readonly) + return ( + +
+ {renderNewCard(group)} +
+
+ ); + }, + }} + ); return ( -
- {items.map(item => { - return
{renderCard(item)}
; - })} - {c.enableNew && !c.state.readonly && ( -
{renderNewCard(group)}
- )} -
+ onDraggableChange(evt, group?.key)} + > + {{ + item: ({ element }: { element: IData }) => ( +
{renderCard(element)}
+ ), + footer: () => { + if (c.enableNew && !c.state.readonly) + return ( +
{renderNewCard(group)}
+ ); + }, + }} +
); }; @@ -428,7 +488,7 @@ export const DataViewControl = defineComponent({
{group.children.length > 0 ? ( - renderCardLayout(group.children, group) + renderCardLayout(group.children, group, !c.state.draggable) ) : (
{ibiz.i18n.t('app.noData')} @@ -451,6 +511,8 @@ export const DataViewControl = defineComponent({ } return renderCardLayout( isCollapse.value ? c.state.items.slice(0, c.state.size) : c.state.items, + undefined, + !c.enableEditOrder, ); }; -- Gitee From 572d810a63fa6799f7a4ecc587f3c7629ab32a95 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 18:36:33 +0800 Subject: [PATCH 3/8] =?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 --- CHANGELOG.md | 1 + src/control/list/list.scss | 26 ++++++--- src/control/list/list.tsx | 106 +++++++++++++++++++++++++++++++------ 3 files changed, 110 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d06d8a2a..0842c803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - 新增看板分页栏 - 新增卡片拖拽 +- 新增列表新建和拖拽功能 ## [0.7.41-alpha.16] - 2025-07-30 diff --git a/src/control/list/list.scss b/src/control/list/list.scss index 78512708..f150cd44 100644 --- a/src/control/list/list.scss +++ b/src/control/list/list.scss @@ -20,9 +20,9 @@ $control-list-group-style2: ( color: getCssVar(control-list, text-color); cursor: pointer; background-color: getCssVar(control-list, item-bg-color); - + @include e(right) { - padding: 0 getCssVar(spacing, extra,tight); + padding: 0 getCssVar(spacing, extra, tight); @include m(actions) { display: flex; justify-content: center; @@ -33,7 +33,7 @@ $control-list-group-style2: ( &.is-has-caption { margin: 0; } - } + } } } @@ -45,6 +45,15 @@ $control-list-group-style2: ( flex: 1; } } + + @include e(new) { + display: flex; + align-items: center; + justify-content: center; + color: getCssVar(color, text, 3); + margin-top: getCssVar(spacing, tight); + border: 2px dashed getCssVar(color, border); + } } @include b(control-list) { @@ -106,7 +115,7 @@ $control-list-group-style2: ( @include b(control-list-content) { flex-grow: 1; } - + @include e(batchtoolbar) { margin: getCssVar(spacing, tight) 0; } @@ -162,7 +171,10 @@ $control-list-group-style2: ( } @include b('control-list-group-style2') { - @include set-component-css-var(control-list-group-style2, $control-list-group-style2); + @include set-component-css-var( + control-list-group-style2, + $control-list-group-style2 + ); @include e('header') { display: flex; @@ -174,7 +186,7 @@ $control-list-group-style2: ( font-size: getCssVar('font-size', 'regular'); color: getCssVar(control-list-group-style2, color); } - + &::before, &::after { display: block; @@ -186,4 +198,4 @@ $control-list-group-style2: ( transform: translateY(-50%); } } -} \ No newline at end of file +} diff --git a/src/control/list/list.tsx b/src/control/list/list.tsx index 571d8c97..ce0c5e57 100644 --- a/src/control/list/list.tsx +++ b/src/control/list/list.tsx @@ -1,10 +1,10 @@ /* eslint-disable no-return-assign */ /* eslint-disable no-nested-ternary */ import { - hasEmptyPanelRenderer, + useNamespace, IBizCustomRender, useControlController, - useNamespace, + hasEmptyPanelRenderer, } from '@ibiz-template/vue3-util'; import { defineComponent, PropType, computed, VNode, ref, watch } from 'vue'; import { IDEList, ILayoutPanel, IUIActionGroupDetail } from '@ibiz/model-core'; @@ -12,16 +12,21 @@ import { isNil } from 'lodash-es'; import { createUUID } from 'qx-util'; import { ControlVO, + ListController, + IDragChangeInfo, IControlProvider, IMDControlGroupState, - ListController, } from '@ibiz-template/runtime'; -import './list.scss'; +import draggable from 'vuedraggable'; import { showTitle } from '@ibiz-template/core'; import { usePagination } from '../../util'; +import './list.scss'; export const ListControl = defineComponent({ name: 'IBizListControl', + components: { + draggable, + }, props: { /** * @description 列表模型数据 @@ -117,6 +122,34 @@ export const ListControl = defineComponent({ }, ); + let cacheInfo: Partial | null = null; + + const onDraggableChange = (evt: IData, groupKey?: string | number) => { + if (evt.moved) { + // 排序 + c.onDragChange({ + from: groupKey!, + to: groupKey!, + fromIndex: evt.moved.oldIndex, + toIndex: evt.moved.newIndex, + }); + } + if (evt.added) { + cacheInfo = { + to: groupKey, + toIndex: evt.added.newIndex, + }; + } + if (evt.removed) { + if (cacheInfo) { + cacheInfo.from = groupKey; + cacheInfo.fromIndex = evt.removed.oldIndex; + c.onDragChange(cacheInfo as IDragChangeInfo); + } + cacheInfo = null; + } + }; + // 本地数据模式 const initSimpleData = (): void => { if (!props.data) { @@ -231,6 +264,25 @@ export const ListControl = defineComponent({ ); }; + /** + * @description 绘制新建项 + * @param {IMDControlGroupState} [group] + * @returns {*} + */ + const renderNewItem = (group?: IMDControlGroupState) => { + return ( +
{ + c.onClickNew(event, group?.key); + }} + > + +
+ ); + }; + // 绘制默认列表项 const renderDefaultItem = (item: IData): VNode => { const actionModel = c.getOptItemModel(); @@ -337,18 +389,38 @@ export const ListControl = defineComponent({ * * @param {IData[]} items */ - const renderListItems = (items: IData[]) => { + const renderListItems = ( + items: IData[], + group?: IMDControlGroupState, + disabled: boolean = true, + ) => { const { navAppViewId } = c.model; - return items.map(item => { - if (navAppViewId && c.state.showRowDetail) - return ( -
- {renderItem(item)} - {item.__isExpand && renderRowDetail(item)} -
- ); - return renderItem(item); - }); + return ( + onDraggableChange(evt, group?.key)} + > + {{ + item: ({ element }: { element: IData }) => { + if (navAppViewId && c.state.showRowDetail) + return ( +
+ {renderItem(element)} + {element.__isExpand && renderRowDetail(element)} +
+ ); + return renderItem(element); + }, + footer: () => { + if (c.enableNew && !c.state.readonly) return renderNewItem(group); + }, + }} +
+ ); }; /** @@ -378,7 +450,7 @@ export const ListControl = defineComponent({ }, default: () => group.children.length > 0 ? ( - renderListItems(group.children) + renderListItems(group.children, group, !c.state.draggable) ) : (
{ibiz.i18n.t('app.noData')} @@ -469,6 +541,8 @@ export const ListControl = defineComponent({ isCollapse.value ? c.state.items.slice(0, c.state.size) : c.state.items, + undefined, + !c.enableEditOrder, )}
); -- Gitee From a0c7b1db549ac9c553ad77cff1489831ced12dff Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 18:36:58 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E5=8A=A8=E6=80=81=E6=8E=A7=E4=BB=B6=E5=8F=82=E6=95=B0?= =?UTF-8?q?grouprowmode=EF=BC=8C=E6=8E=A7=E5=88=B6=E5=88=86=E7=BB=84?= =?UTF-8?q?=E8=A1=8C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/control/grid/grid/grid-control.util.ts | 53 +++++++++++++++------- src/control/grid/grid/grid.scss | 6 +++ src/control/grid/grid/grid.tsx | 16 +++++-- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0842c803..6aecd864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - 新增看板分页栏 - 新增卡片拖拽 - 新增列表新建和拖拽功能 +- 新增表格动态控件参数grouprowmode,控制分组行模式 ## [0.7.41-alpha.16] - 2025-07-30 diff --git a/src/control/grid/grid/grid-control.util.ts b/src/control/grid/grid/grid-control.util.ts index c5193673..234ef18f 100644 --- a/src/control/grid/grid/grid-control.util.ts +++ b/src/control/grid/grid/grid-control.util.ts @@ -378,7 +378,7 @@ export function useITableEvent(c: GridController): { ): Promise { // 新建行拦截行点击事件 if (data.srfuf === Srfuf.CREATE) { - if (c.editShowMode === 'row') { + if (c.editShowMode === 'row' && !data.isGroupRow) { const row = c.findRowState(data); // 新建行值被修改过就保存,否则取消 if (row) await c.switchRowEdit(row); @@ -406,6 +406,10 @@ export function useITableEvent(c: GridController): { _column: IData, event: MouseEvent, ): Promise { + if (data.isGroupRow) { + tableRef.value?.store.loadOrToggle(data); + return; + } // 非shift点击时需标记选中数据 if (!event.shiftKey) { const index = c.findRowStateIndex(data); @@ -444,9 +448,7 @@ export function useITableEvent(c: GridController): { function onDbRowClick(data: ControlVO): void { // 新建行拦截行双击事件 - if (data.srfuf === Srfuf.CREATE) { - return; - } + if (data.srfuf === Srfuf.CREATE || data.isGroupRow) return; c.onDbRowClick(data); } @@ -745,21 +747,28 @@ export function useAppGridBase( const tableData = computed(() => { const state = c.state; if (c.state.enableGroup) { + const grouprowmode = c.controlParams.grouprowmode; const result: IData[] = []; state.groups.forEach(item => { - if (!item.children.length) { - return; + if (!item.children.length) return; + if (grouprowmode === 'NEWROW') { + result.push({ + ...item, + tempsrfkey: item.key, + isGroupRow: true, + }); + } else { + const children = [...item.children]; + const first = children.shift(); + result.push({ + tempsrfkey: first?.tempsrfkey || item.caption, + srfkey: first?.srfkey || item.caption, + isGroupData: true, + caption: item.caption, + first, + children, + }); } - const children = [...item.children]; - const first = children.shift(); - result.push({ - tempsrfkey: first?.tempsrfkey || item.caption, - srfkey: first?.srfkey || item.caption, - isGroupData: true, - caption: item.caption, - first, - children, - }); }); return result; } @@ -967,6 +976,18 @@ export function useAppGridBase( colspan, }; } + // 设置分组行的合并 + if (row.isGroupRow) { + const total = c.state.singleSelect + ? renderColumns.value.length + : renderColumns.value.length + 1; + const index = c.state.singleSelect ? 0 : 1; + if (columnIndex === index) return { rowspan: 1, colspan: total }; + return { + rowspan: 0, + colspan: 0, + }; + } }; /** diff --git a/src/control/grid/grid/grid.scss b/src/control/grid/grid/grid.scss index f2d18520..5d4712e8 100644 --- a/src/control/grid/grid/grid.scss +++ b/src/control/grid/grid/grid.scss @@ -138,6 +138,12 @@ $control-grid-footer: ( } } + @include when(group-row-mode) { + .el-table .el-table__body-wrapper .el-table__row .el-table__expand-icon { + margin: 0 0 0 20px; + } + } + @include e(add) { width: calc(100% - getCssVar(spacing, extra-tight)); margin: getCssVar(spacing, tight) getCssVar(spacing, extra-tight); diff --git a/src/control/grid/grid/grid.tsx b/src/control/grid/grid/grid.tsx index 1112ad39..b0ae02c9 100644 --- a/src/control/grid/grid/grid.tsx +++ b/src/control/grid/grid/grid.tsx @@ -255,11 +255,13 @@ export function renderColumn( }, default: ({ row }: IData): VNode | null => { let elRow = row; // element表格数据 - if (row.isGroupData) { - // 有第一条数据时,分组那一行绘制第一条数据 - elRow = row.first; - } - + if (elRow.isGroupRow) + return ( +
+ {row.caption} +
+ ); + if (row.isGroupData) elRow = row.first; const rowState = c.findRowState(elRow); if (rowState) { // 常规非业务单元格由表格绘制(性能优化) @@ -678,6 +680,10 @@ export const GridControl = defineComponent({ this.ns.is('single-select', state.singleSelect), this.ns.is('empty', state.items.length === 0), this.ns.is('enable-customized', this.c.model.enableCustomized), + this.ns.is( + 'group-row-mode', + this.c.controlParams.grouprowmode === 'NEWROW', + ), ]} controller={this.c} style={this.headerCssVars} -- Gitee From fd67e4fcf2b5686f02ad246d52644d102109cdca Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 18:37:42 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=99=A8=E8=A1=A8=E5=8D=95=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ .../mdctrl-container/mdctrl-container.scss | 11 ++++++++++ .../mdctrl-container/mdctrl-container.tsx | 22 ++++++++----------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aecd864..068446d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ - 新增列表新建和拖拽功能 - 新增表格动态控件参数grouprowmode,控制分组行模式 +### Changed + +- 优化重复器表单样式 + ## [0.7.41-alpha.16] - 2025-07-30 ### Added diff --git a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss index 67239bee..32e211eb 100644 --- a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss +++ b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss @@ -14,6 +14,17 @@ $mdctrl-container-item: ( flex-grow: 1; } + @include e('left') { + flex-shrink: 0; + gap: getCssVar('spacing', 'tight'); + @include flex(row); + } + + @include e('right') { + flex-shrink: 0; + width: 80px; + } + @include e('icon-drag') { display: flex; align-items: center; diff --git a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx index 5d99ba34..6a01cea3 100644 --- a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx +++ b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx @@ -96,9 +96,7 @@ export const MDCtrlContainer = defineComponent({ }; const renderRemoveBtn = (item: IData, index: number) => { - if (!props.enableDelete) { - return null; - } + if (!props.enableDelete) return null; if (ibiz.config.form.mdCtrlConfirmBeforeRemove) { return ( { - if (!props.enableDelete) { - return null; - } + if (!props.enableDelete) return null; if (ibiz.config.form.mdCtrlConfirmBeforeRemove) { return ( - {props.enableSort && renderDragBtn()} +
+ {props.enableSort && renderDragBtn()} + {props.enableDelete && renderRemoveBtn(item, index)} +
{formComponent} - {showActions.value && ( -
- {index === 0 && props.enableCreate && renderAddBtn()} - {renderRemoveBtn(item, index)} -
- )} +
+ {index === 0 && props.enableCreate && renderAddBtn()} +
); }; -- Gitee From 57962a2ced40f90b5fe82ad62f4060d7144020b5 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 18:38:41 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=99=A8=E8=A1=A8=E6=A0=BC=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + package.json | 2 + .../repeater-grid/repeater-grid.scss | 12 ++++ .../repeater-grid/repeater-grid.tsx | 68 ++++++++++++++++--- 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 068446d2..dbfd405b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - 新增卡片拖拽 - 新增列表新建和拖拽功能 - 新增表格动态控件参数grouprowmode,控制分组行模式 +- 新增重复器表格排序 ### Changed diff --git a/package.json b/package.json index f7e63a6f..99d1aec9 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "vue-i18n": "^9.6.5", "vue-router": "^4.2.5", "vuedraggable": "^4.1.0", + "sortablejs": "^1.15.6", "snabbdom": "^3.3.1", "xlsx": "^0.18.5" }, @@ -74,6 +75,7 @@ "@commitlint/config-conventional": "^18.5.0", "@ibiz-template/cli": "^0.3.10", "@types/lodash-es": "^4.17.12", + "@types/sortablejs": "^1.15.8", "@types/node": "^20.11.5", "@types/nprogress": "^0.2.3", "@types/qs": "^6.9.11", diff --git a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.scss b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.scss index 63970537..76b56ff8 100644 --- a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.scss +++ b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.scss @@ -16,6 +16,18 @@ $repeater-grid: ( z-index: 2; } + @include e(drag-icon) { + cursor: move; + } + + @include e(sortable-ghost) { + opacity: 0.5; + } + + .el-table__row { + height: 57px; + } + .el-table__row:hover { .#{bem(repeater-grid-index, text)}{ &:last-child{ diff --git a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx index c4e2221e..b101239f 100644 --- a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx +++ b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx @@ -1,17 +1,20 @@ import { - defineComponent, h, - reactive, - resolveComponent, + ref, toRaw, watch, + reactive, + defineComponent, + resolveComponent, + onMounted, } from 'vue'; +import Sortable from 'sortablejs'; import { + EventBase, ControlVO, EditFormController, - EventBase, - FormMDCtrlRepeaterController, IEditFormController, + FormMDCtrlRepeaterController, } from '@ibiz-template/runtime'; import { useCtx, useNamespace } from '@ibiz-template/vue3-util'; import { recursiveIterate, showTitle } from '@ibiz-template/core'; @@ -29,10 +32,12 @@ export const RepeaterGrid: ReturnType = defineComponent( }, emits: { change: (_value: IData[]) => true, + dragChange: (_draggedIndex?: number, _targetIndex?: number) => true, }, setup(props, { emit }) { const ns = useNamespace('repeater-grid'); const formItems: IDEFormItem[] = []; + const tableRef = ref(); // 遍历所有的项,如果有逻辑的话加入 recursiveIterate( props.controller.repeatedForm, @@ -115,6 +120,25 @@ export const RepeaterGrid: ReturnType = defineComponent( { immediate: true, deep: true }, ); + const rowDrop = () => { + const wrapper = tableRef.value?.$el?.querySelector( + '.el-table__body-wrapper tbody', + ); + if (!wrapper || !props.controller.enableSort) return; + Sortable.create(wrapper, { + animation: 150, + handle: `.${ns.e('drag-icon')}`, + ghostClass: `${ns.e('sortable-ghost')}`, + onEnd({ newIndex, oldIndex }) { + emit('dragChange', oldIndex, newIndex); + }, + }); + }; + + onMounted(() => { + setTimeout(() => rowDrop()); + }); + const renderRemoveBtn = (index: number) => { if (!props.controller.enableDelete) { return null; @@ -157,7 +181,7 @@ export const RepeaterGrid: ReturnType = defineComponent( ); }; - return { ns, formItems, formControllers, renderRemoveBtn }; + return { ns, tableRef, formItems, formControllers, renderRemoveBtn }; }, render() { return ( @@ -173,14 +197,40 @@ export const RepeaterGrid: ReturnType = defineComponent( )} { - // 索引单元格样式 - return columnIndex === 0 ? this.ns.b('index') : ''; + const shouldShowIndex = this.controller.enableSort + ? columnIndex === 1 + : columnIndex === 0; + return shouldShowIndex ? this.ns.b('index') : ''; }} > + {this.controller.enableSort && ( + + {{ + default: () => ( + + + + + + + + ), + }} + + )} {{ default: (opts: IData) => { -- Gitee From d8a7da1c2c96c3c163ff344b07be04f2cec9999b Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 1 Aug 2025 19:48:27 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=99=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form-mdctrl-repeater/repeater-grid/repeater-grid.tsx | 8 ++++---- .../form-mdctrl/mdctrl-container/mdctrl-container.scss | 5 ++--- .../form-mdctrl/mdctrl-container/mdctrl-container.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx index b101239f..04d616bc 100644 --- a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx +++ b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx @@ -4,9 +4,10 @@ import { toRaw, watch, reactive, + nextTick, + onMounted, defineComponent, resolveComponent, - onMounted, } from 'vue'; import Sortable from 'sortablejs'; import { @@ -32,7 +33,6 @@ export const RepeaterGrid: ReturnType = defineComponent( }, emits: { change: (_value: IData[]) => true, - dragChange: (_draggedIndex?: number, _targetIndex?: number) => true, }, setup(props, { emit }) { const ns = useNamespace('repeater-grid'); @@ -130,13 +130,13 @@ export const RepeaterGrid: ReturnType = defineComponent( handle: `.${ns.e('drag-icon')}`, ghostClass: `${ns.e('sortable-ghost')}`, onEnd({ newIndex, oldIndex }) { - emit('dragChange', oldIndex, newIndex); + props.controller.dragChange(oldIndex!, newIndex!); }, }); }; onMounted(() => { - setTimeout(() => rowDrop()); + nextTick(() => rowDrop()); }); const renderRemoveBtn = (index: number) => { diff --git a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss index 32e211eb..66e40475 100644 --- a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss +++ b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.scss @@ -16,13 +16,12 @@ $mdctrl-container-item: ( @include e('left') { flex-shrink: 0; - gap: getCssVar('spacing', 'tight'); - @include flex(row); } @include e('right') { flex-shrink: 0; - width: 80px; + width: 60px; + margin-left: getCssVar('spacing', 'tight'); } @include e('icon-drag') { diff --git a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx index 6a01cea3..9e690a80 100644 --- a/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx +++ b/src/control/form/form-detail/form-mdctrl/mdctrl-container/mdctrl-container.tsx @@ -214,9 +214,9 @@ export const MDCtrlContainer = defineComponent({
{props.enableSort && renderDragBtn()} - {props.enableDelete && renderRemoveBtn(item, index)}
{formComponent} + {renderRemoveBtn(item, index)}
{index === 0 && props.enableCreate && renderAddBtn()}
@@ -285,13 +285,13 @@ export const MDCtrlContainer = defineComponent({ if (this.items?.length) { defaultContent = this.enableSort ? ( {{ item: ({ element, index }: { element: IData; index: number }) => -- Gitee From ec35c9371e062e1ccc6a47039d541bcd8f94ede0 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Sun, 3 Aug 2025 12:15:11 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E5=99=A8=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repeater-grid/repeater-grid.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx index 04d616bc..bfe82179 100644 --- a/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx +++ b/src/control/form/form-detail/form-mdctrl/form-mdctrl-repeater/repeater-grid/repeater-grid.tsx @@ -17,6 +17,7 @@ import { IEditFormController, FormMDCtrlRepeaterController, } from '@ibiz-template/runtime'; +import { createUUID } from 'qx-util'; import { useCtx, useNamespace } from '@ibiz-template/vue3-util'; import { recursiveIterate, showTitle } from '@ibiz-template/core'; import { IDEFormDetail, IDEFormItem } from '@ibiz/model-core'; @@ -38,6 +39,8 @@ export const RepeaterGrid: ReturnType = defineComponent( const ns = useNamespace('repeater-grid'); const formItems: IDEFormItem[] = []; const tableRef = ref(); + const tableKey = ref(createUUID()); + // 遍历所有的项,如果有逻辑的话加入 recursiveIterate( props.controller.repeatedForm, @@ -131,6 +134,7 @@ export const RepeaterGrid: ReturnType = defineComponent( ghostClass: `${ns.e('sortable-ghost')}`, onEnd({ newIndex, oldIndex }) { props.controller.dragChange(oldIndex!, newIndex!); + tableKey.value = createUUID(); }, }); }; @@ -181,7 +185,14 @@ export const RepeaterGrid: ReturnType = defineComponent( ); }; - return { ns, tableRef, formItems, formControllers, renderRemoveBtn }; + return { + ns, + tableRef, + tableKey, + formItems, + formControllers, + renderRemoveBtn, + }; }, render() { return ( @@ -198,6 +209,7 @@ export const RepeaterGrid: ReturnType = defineComponent( )}