diff --git a/CHANGELOG.md b/CHANGELOG.md index 6040a5ceaa29cd3acc0134ef36581fe540a1e940..76006bbd5c607518669f669e18c9a8e8a80ab9d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ - 新增卡片新建功能 - 新增扩展菜单常规模式 - 新增列表项行为绘制 +- 新增看板分页栏 +- 新增卡片拖拽功能 ### Fixed diff --git a/src/control/data-view/data-view.tsx b/src/control/data-view/data-view.tsx index 00eb9bb559a29938572aa48e55e89cabe33c0bb8..f31e59e7d7b2b8976ca490e1e306e4402134a251 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, ); }; diff --git a/src/control/kanban/kanban.scss b/src/control/kanban/kanban.scss index 08cf95fb20255fe4db32639b5b10f57d7db99113..5c372a2effa175f8741a2b8d5f54127bf635bf26 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 39947e8cba940482584eb26af3f8a13c5c35ff78..0995ad9fd1e2420587281112eee1acffd01a118c 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 a18cb279c82fff17f6b10d427d3c2dfb6cb8ec6b..9b17dc399dee633c040255593f4b73607d26529f 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 09a2378d7d44d5c89f3e85875668e5bf80f28c65..ecc9b324ff4891de8473d4ac6476b6ee6f6bef28 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 (