diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e64dfa6581e4babd6135c3e059c4f70309331fb..dd17a4309bf5d761b97ec3999569618c63b9a69a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 新增列表部件样式-扩展视图3:仅非分组列表和分组样式2列表支持,实现列表的从下往上的绘制,同时滚动加载也支持上滚加载 + ## [0.7.41-alpha.24] - 2025-09-04 ### Added diff --git a/src/control/list/list.scss b/src/control/list/list.scss index 72dea665315ba67d8c000c2711260fbab4da638b..123614d84467d879c67186111b7cb512964fed8a 100644 --- a/src/control/list/list.scss +++ b/src/control/list/list.scss @@ -142,6 +142,15 @@ $control-list-group-style2: ( overflow: auto; } +@include b(control-list-reverse-scroll) { + height: 100%; + overflow: auto; + .#{bem(control-list, layout-flex)} { + display: flex; + flex-direction: column-reverse + } +} + @include b(control-list-group-content) { @include e(item-header) { @include flex(row, space-between, center); diff --git a/src/control/list/list.tsx b/src/control/list/list.tsx index be5b6ffd1c33234d4ace91dc37795fd9a2fd981e..23e519289032913bbc4135414138fd868302ef47 100644 --- a/src/control/list/list.tsx +++ b/src/control/list/list.tsx @@ -10,7 +10,6 @@ import { import { ref, VNode, watch, PropType, computed, defineComponent } from 'vue'; import { IDEList, ILayoutPanel, IUIActionGroupDetail } from '@ibiz/model-core'; import { isNil } from 'lodash-es'; -import { createUUID } from 'qx-util'; import { ControlVO, ListController, @@ -20,6 +19,7 @@ import { } from '@ibiz-template/runtime'; import draggable from 'vuedraggable'; import { showTitle } from '@ibiz-template/core'; +import { debounce } from 'lodash-es'; import { usePagination } from '../../util'; import './list.scss'; @@ -69,21 +69,31 @@ export const ListControl = defineComponent({ loadDefault: { type: Boolean, default: true }, }, setup(props) { - const c = useControlController((...args) => new ListController(...args)); + const c: ListController = useControlController( + (...args) => new ListController(...args), + ); const ns = useNamespace(`control-${c.model.controlType!.toLowerCase()}`); useControlPopoverzIndex(c); const { onPageChange, onPageRefresh, onPageSizeChange } = usePagination(c); - // 是否可以加载更多 - const isLodeMoreDisabled = computed(() => { - if (c.model.enablePagingBar === true) { - return true; - } - if (c.model.pagingMode !== 2) { - return true; - } + /** + * 无限滚动元素 + */ + const infiniteScroll = ref(); + + /** + * 是否为反向滚动条 + */ + const reverseScroll = c.model.controlStyle === 'EXTVIEW3'; + + /** + * 禁用加载更多 + */ + const disabledLodeMore = computed(() => { + if (c.model.enablePagingBar === true) return true; + if (c.model.pagingMode !== 2) return true; return ( c.state.items.length >= c.state.total || c.state.isLoading || @@ -102,28 +112,6 @@ export const ListControl = defineComponent({ (c.model.pagingMode === 2 || c.model.pagingMode === 3) ); }); - // 无限滚动元素 - const infiniteScroll = ref(); - // 无限滚动元素标识 - const infiniteScrollKey = ref(createUUID()); - - watch( - () => c.state.curPage, - () => { - if ( - c.state.curPage === 1 && - (c.model.pagingMode === 2 || c.model.pagingMode === 3) - ) { - infiniteScrollKey.value = createUUID(); - const containerEl = - infiniteScroll.value?.ElInfiniteScroll?.containerEl; - if (containerEl) { - containerEl.lastScrollTop = 0; - containerEl.scrollTop = 0; - } - } - }, - ); let cacheInfo: Partial | null = null; @@ -176,11 +164,46 @@ export const ListControl = defineComponent({ } }); - // 平滑滚动到顶部 - const scrollToTop = () => { + /** + * @description 平滑滚动到顶部 + */ + const scrollToTop = (): void => { infiniteScroll.value?.scrollTo({ top: 0, behavior: 'smooth' }); }; + /** + * @description 处理滚动加载 + * @returns {*} {Promise} + */ + const handleScrollLoad = async (): Promise => { + if (!infiniteScroll.value || disabledLodeMore.value) return; + const scrollTop = infiniteScroll.value.scrollTop; + const scrollHeight = infiniteScroll.value.scrollHeight; + const clientHeight = infiniteScroll.value.clientHeight; + if (!reverseScroll && scrollHeight - scrollTop - clientHeight < 10) { + // 滚动到底部加载更多 + await c.loadMore(); + } else if (reverseScroll && scrollTop < 10) { + // 滚动到顶部部加载更多 + await c.loadMore(); + // 恢复滚动位置,保持用户体验连续性 + const newScrollHeight = infiniteScroll.value.scrollHeight; + infiniteScroll.value.scrollTop = + scrollTop + (newScrollHeight - scrollHeight); + } + }; + + c.evt.on('onLoadSuccess', evt => { + if (evt.isInitialLoad && reverseScroll) { + setTimeout(() => { + if (!infiniteScroll.value) return; + const scrollHeight = infiniteScroll.value.scrollHeight; + const clientHeight = infiniteScroll.value.clientHeight; + infiniteScroll.value.scrollTop = scrollHeight + clientHeight; + }, 100); + } + }); + c.evt.on('onScrollToTop', () => { scrollToTop(); }); @@ -233,13 +256,9 @@ export const ListControl = defineComponent({ watch( () => props.data, () => { - if (props.isSimple) { - initSimpleData(); - } - }, - { - deep: true, + if (props.isSimple) initSimpleData(); }, + { deep: true }, ); // 绘制项布局面板 @@ -494,46 +513,42 @@ export const ListControl = defineComponent({ * 绘制分组样式2项 * @return {*} {VNode[]} */ - const renderGroupStyle2 = (): VNode[] => { - return c.state.groups?.map(group => { - return ( -
-
-
- {showTitle(group.caption)} -
-
-
- {group.children.length > 0 ? ( - renderListItems(group.children) - ) : ( -
- {ibiz.i18n.t('app.noData')} + const renderGroupStyle2 = (): VNode => { + return ( +
+ {c.state.groups?.map(group => { + return ( +
+
+
+ {showTitle(group.caption)} +
- )} -
-
- ); - }); +
+ {group.children.length > 0 ? ( + renderListItems(group.children) + ) : ( +
+ {ibiz.i18n.t('app.noData')} +
+ )} +
+
+ ); + })} +
+ ); }; // 绘制列表内容 const renderListContent = (): VNode => { - if (c.state.enableGroup && !c.state.isSimple) { - if (c.model.groupStyle === 'STYLE2') { - return ( -
- {renderGroupStyle2()} -
- ); - } - + if ( + c.state.enableGroup && + !c.state.isSimple && + c.model.groupStyle !== 'STYLE2' + ) { return ( => c.loadMore()} - infinite-scroll-distance={10} - infinite-scroll-disabled={isLodeMoreDisabled.value} - ref={'infiniteScroll'} - key={infiniteScrollKey.value} + onScroll={debounce(handleScrollLoad, 300)} > - {renderListItems( - isCollapse.value - ? c.state.items.slice(0, c.state.size) - : c.state.items, - undefined, - !c.enableEditOrder, - )} + {c.state.enableGroup && c.model.groupStyle === 'STYLE2' + ? renderGroupStyle2() + : renderListItems( + isCollapse.value + ? c.state.items.slice(0, c.state.size) + : c.state.items, + undefined, + !c.enableEditOrder, + )}
); }; @@ -678,41 +697,39 @@ export const ListControl = defineComponent({ // 当为点击加载时,页面底部会有一个继续加载按钮图标,点击时就会继续加载后续数据,直到完全加载完数据后,[加载更多]图标会隐藏, // 折叠图标会显示,点击折叠图标会折叠数据,折叠后,显示展开图标,点击展开图标,已加载的数据会完整显示 const renderCollapseExpandIcon = () => { - let icon = null; - const loadMore = !( - c.state.items.length >= c.state.total || c.state.total <= c.state.size - ); - if (showCollapseOrExpandIcon.value) { - if (c.model.pagingMode === 2) { - if (isCollapse.value) { - icon = downIcon(); - } else if (c.state.items.length > c.state.size) { - icon = upIcon(); - } - } - if (c.model.pagingMode === 3) { - if (isCollapse.value) { - icon = downIcon(); - } else if (loadMore) { - icon = loadMoreIcon(); - } else if (c.state.isCreated && c.state.items.length > c.state.size) { - icon = upIcon(); - } - } + if (!showCollapseOrExpandIcon.value) return null; + + const { pagingMode } = c.model; + const { items, total, size, isCreated } = c.state; + const hasMoreItems = items.length < total && total > size; + const exceedsInitialSize = items.length > size; + + if (pagingMode === 2) { + if (isCollapse.value) return reverseScroll ? upIcon() : downIcon(); + if (exceedsInitialSize) return reverseScroll ? downIcon() : upIcon(); + } + + if (pagingMode === 3) { + if (isCollapse.value) return reverseScroll ? upIcon() : downIcon(); + if (hasMoreItems) return loadMoreIcon(); + if (isCreated && exceedsInitialSize) + return reverseScroll ? downIcon() : upIcon(); } - return icon; + + return null; }; return { c, ns, + reverseScroll, infiniteScroll, - renderListContent, renderNoData, - renderBatchToolBar, onPageChange, onPageRefresh, onPageSizeChange, + renderListContent, + renderBatchToolBar, renderCollapseExpandIcon, }; }, @@ -742,9 +759,10 @@ export const ListControl = defineComponent({ return ( + {this.reverseScroll && this.renderCollapseExpandIcon()} {content} {this.c.state.enableNavView && this.c.state.showNavIcon ? ( !this.c.state.showNavView ? ( @@ -763,7 +781,7 @@ export const ListControl = defineComponent({ > ) ) : null} - {this.renderCollapseExpandIcon()} + {!this.reverseScroll && this.renderCollapseExpandIcon()} );