From 9450aefc8a80668ca19f7e1a07cb2b7c7b9cd83c Mon Sep 17 00:00:00 2001 From: lijisanxiong <1518062161@qq.com> Date: Fri, 26 Sep 2025 20:06:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat=EF=BC=9A=E6=95=B0=E6=8D=AE=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=BC=96=E8=BE=91=E5=99=A8=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=A1=AB=E6=A8=A1=E5=BC=8F=E9=85=8D=E7=BD=AE=E7=9A=84=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=9B=B4=E5=A4=9A=E3=80=81=E6=BB=9A=E5=8A=A8=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data-picker/ibiz-picker/ibiz-picker.scss | 10 ++ .../data-picker/ibiz-picker/ibiz-picker.tsx | 153 +++++++++++++++++- .../data-picker/picker-editor.controller.ts | 108 ++++++++++++- src/locale/en/index.ts | 1 + src/locale/zh-CN/index.ts | 1 + 5 files changed, 264 insertions(+), 9 deletions(-) diff --git a/src/editor/data-picker/ibiz-picker/ibiz-picker.scss b/src/editor/data-picker/ibiz-picker/ibiz-picker.scss index e12ddbad..0c14ed81 100644 --- a/src/editor/data-picker/ibiz-picker/ibiz-picker.scss +++ b/src/editor/data-picker/ibiz-picker/ibiz-picker.scss @@ -128,6 +128,16 @@ $picker: ( background-color: getCssVar(picker, empty-bg-color); } } + li:has(.#{bem(picker__loadmore)}) { + padding: 0; + } + } + + @include e(loadmore) { + display: flex; + align-items: center; + justify-content: center; + color: getCssVar(color, link); } @include e(suffix) { diff --git a/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx index cae251ce..e24e5fd8 100644 --- a/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx +++ b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx @@ -8,6 +8,7 @@ import { onMounted, defineComponent, resolveComponent, + onBeforeUnmount, } from 'vue'; import { renderString, @@ -16,6 +17,7 @@ import { getDataPickerProps, } from '@ibiz-template/vue3-util'; import { isEmpty, isNil } from 'ramda'; +import { debounce } from 'lodash-es'; import { showTitle } from '@ibiz-template/core'; import { IAppDEUIActionGroupDetail } from '@ibiz/model-core'; import { PickerEditorController } from '../picker-editor.controller'; @@ -193,6 +195,25 @@ export const IBizPicker = defineComponent({ } }; + // 更新下拉列表数据回调方法 + let listCallback: (_items: IData[]) => void | void; + // 搜索值 + let searchQuery = ''; + + // 更新下拉列表数据 + const handleCallback = (cb: (_items: IData[]) => void) => { + const callbackItems: IData[] = items.value.length + ? [...items.value] + : [{ srftype: 'empty' }]; + actionPostion === 'top' + ? callbackItems.unshift(...c.actionDetails) + : callbackItems.push(...c.actionDetails); + + if (c.isShowLoadMore) { + callbackItems.push({ srftype: 'loadmore' }); + } + cb(callbackItems); + }; // 搜索 const onSearch = async (query: string, cb?: (_items: IData[]) => void) => { if (c.model.appDataEntityId) { @@ -205,13 +226,27 @@ export const IBizPicker = defineComponent({ items.value = res.data as IData[]; isLoaded.value = true; if (cb && cb instanceof Function) { - const callbackItems: IData[] = items.value.length - ? [...items.value] - : [{ srftype: 'empty' }]; - actionPostion === 'top' - ? callbackItems.unshift(...c.actionDetails) - : callbackItems.push(...c.actionDetails); - cb(callbackItems); + searchQuery = trimQuery; + listCallback = cb; + handleCallback(cb); + } + } + } + }; + + /** + * 加载更多 + * @return {*} {Promise} + */ + const loadMore = async (): Promise => { + if (c.total > items.value.length && c.model.appDataEntityId) { + const res = await c.getServiceData(searchQuery, props.data, { + isLoadMore: true, + }); + if (res) { + items.value = [...items.value, ...(res.data as IData[])]; + if (listCallback) { + handleCallback(listCallback); } } } @@ -222,7 +257,11 @@ export const IBizPicker = defineComponent({ isShowAll.value = true; setEditable(false); // 回车选中空白 - if (item.srftype === 'empty' || item.detailType === 'DEUIACTION') + if ( + item.srftype === 'empty' || + item.detailType === 'DEUIACTION' || + item.srftype === 'loadmore' + ) return resetCurValue(); await handleDataSelect(item); }; @@ -290,6 +329,74 @@ export const IBizPicker = defineComponent({ { immediate: true }, ); + // 加载更多Ref + const loadmoreRef = ref(); + + // 是否关闭弹窗 + const isClosePopper = ref(false); + // 处理加载更多的点击事件 + const onLoadMoreClick = async (_event: MouseEvent) => { + _event.preventDefault(); + _event.stopPropagation(); + loadMore(); + editorRef.value?.popperRef?.onOpen(); + isClosePopper.value = true; + }; + + // 处理弹窗关闭逻辑 + const handlePopperClose = (_event: MouseEvent) => { + // 点击加载更多 + const isClickInside = loadmoreRef.value?.contains(_event.target); + // 点击输入框 + const isFocus = editorRef.value?.inputRef?.input.contains(_event.target); + if (!isClickInside && !isFocus && isClosePopper.value) { + editorRef.value?.popperRef?.onClose(); + isClosePopper.value = false; + } + }; + + // 获取弹窗中的滚动容器元素 + const getPopperScroll = (): IParams | void => { + return editorRef.value?.popperRef?.popperRef.contentRef.querySelector( + '.el-scrollbar__wrap', + ); + }; + + // 处理滚动加载 + const handleScrollLoad = async () => { + const infiniteScroll = getPopperScroll(); + + // 确保滚动容器存在 + if (!infiniteScroll) return; + + // 获取滚动容器的相关属性 + const { scrollTop, scrollHeight, clientHeight } = infiniteScroll; + + // 计算当前滚动位置与底部的距离(预留20px的缓冲) + const distanceToBottom = scrollHeight - scrollTop - clientHeight; + + // 当滚动到接近底部(距离小于等于20px)且不在加载中时,触发加载 + if (distanceToBottom <= 20) { + await loadMore(); + } + }; + + const debScrollLoad = debounce(handleScrollLoad, 300); + + // 初始化懒加载逻辑 + const initLazyLoad = () => { + switch (c.pagingMode) { + case 3: + document.addEventListener('mouseup', handlePopperClose); + break; + case 2: + getPopperScroll()?.addEventListener('scroll', debScrollLoad); + break; + default: + break; + } + }; + onMounted(() => { watch( () => props.data[c.valueItem], @@ -314,11 +421,26 @@ export const IBizPicker = defineComponent({ emit('change', null, c.model.id, true); } } + + initLazyLoad(); }, { immediate: true }, ); }); + onBeforeUnmount(() => { + switch (c.pagingMode) { + case 3: + document.removeEventListener('mouseup', handlePopperClose); + break; + case 2: + getPopperScroll()?.removeEventListener('scroll', debScrollLoad); + break; + default: + break; + } + }); + const renderActionItem = (detail: IAppDEUIActionGroupDetail) => { if (!c.groupActionState[detail.id!].visible) return; return ( @@ -355,6 +477,18 @@ export const IBizPicker = defineComponent({ ); }; + const renderLoadMore = () => { + return ( +
onLoadMoreClick(_event)} + > + {ibiz.i18n.t('editor.common.loadMore')} +
+ ); + }; + return { ns, c, @@ -374,6 +508,7 @@ export const IBizPicker = defineComponent({ handleKeyUp, setEditable, renderEmpty, + renderLoadMore, openLinkView, openPickUpView, renderActionItem, @@ -491,6 +626,7 @@ export const IBizPicker = defineComponent({ clearable popper-class={[ this.ns.e('transfer'), + this.ns.bm('popper', `${this.c.model.id}`), this.ns.is('empty', !this.items.length), ]} fetch-suggestions={this.onSearch} @@ -507,6 +643,7 @@ export const IBizPicker = defineComponent({ default: ({ item }: { item: IData }) => { if (this.$slots.append) return this.$slots.append({}); if (item.srftype === 'empty') return this.renderEmpty(); + if (item.srftype === 'loadmore') return this.renderLoadMore(); if (item.detailType === 'DEUIACTION') return this.renderActionItem(item as IAppDEUIActionGroupDetail); return itemContent(item); diff --git a/src/editor/data-picker/picker-editor.controller.ts b/src/editor/data-picker/picker-editor.controller.ts index 1509f8f2..e83b0c53 100644 --- a/src/editor/data-picker/picker-editor.controller.ts +++ b/src/editor/data-picker/picker-editor.controller.ts @@ -19,6 +19,7 @@ import { IUIActionGroupDetail, IAppDEUIActionGroupDetail, } from '@ibiz/model-core'; +import { isNumber } from 'lodash-es'; import { mergeDeepLeft } from 'ramda'; /** @@ -118,6 +119,75 @@ export class PickerEditorController extends EditorController { */ public actionDetails: IAppDEUIActionGroupDetail[] = []; + /** + * 当前页 + * + * @type {number} + * @default 1 + * @memberof PickerEditorController + */ + curPage: number = 1; + + /** + * 总条数 + * + * @type {number} + * @default 0 + * @memberof PickerEditorController + */ + public total: number = 0; + + /** + * 总页数 + * + * @type {number} + * @memberof PickerEditorController + */ + public totalPages?: number; + + /** + * 自填模式分页大小 + * + * @type {number} + * @memberof PickerEditorController + */ + public pagingSize?: number; + + /** + * 自填模式分页模式配置:0:不分页,1:分页栏,2:滚动加载,3:加载更多 + * + * @type {number} + * @memberof PickerEditorController + */ + public pagingMode?: number; + + /** + * 是否懒加载 + * + * @readonly + * @type {boolean} + * @memberof PickerEditorController + */ + get isLazyLoad(): boolean { + return isNumber(this.pagingMode) && this.pagingMode !== 0; + } + + /** + * 是否显示加载更多 + * + * @readonly + * @type {boolean} + * @memberof PickerEditorController + */ + get isShowLoadMore(): boolean { + return !!( + this.isLazyLoad && + this.pagingMode === 3 && + this.totalPages && + this.curPage < this.totalPages + ); + } + protected async onInit(): Promise { super.onInit(); this.initParams(); @@ -134,7 +204,14 @@ export class PickerEditorController extends EditorController { ); if (this.deACMode) { // 自填模式相关 - const { minorSortAppDEFieldId, minorSortDir } = this.deACMode; + const { + minorSortAppDEFieldId, + minorSortDir, + pagingMode, + pagingSize, + } = this.deACMode; + this.pagingMode = pagingMode; + this.pagingSize = pagingSize; if (minorSortAppDEFieldId && minorSortDir) { this.sort = `${minorSortAppDEFieldId.toLowerCase()},${minorSortDir.toLowerCase()}`; } @@ -262,6 +339,9 @@ export class PickerEditorController extends EditorController { public async getServiceData( query: string, data: IData, + options?: { + isLoadMore?: boolean; + }, ): Promise> { const { context, params } = this.handlePublicParams( data, @@ -280,6 +360,23 @@ export class PickerEditorController extends EditorController { Object.assign(fixedParams, { query }); } Object.assign(fixedParams, { size: 1000 }); + if (this.isLazyLoad) { + // 初始加载需要重置分页 + const isLoadMore = options?.isLoadMore === true; + if (isLoadMore) { + this.curPage += 1; + } else { + this.curPage = 1; + } + } + + // 是懒加载并且有size才给page和size。size默认值给0就不传分页和大小 + if (this.isLazyLoad && this.pagingSize) { + Object.assign(fixedParams, { + size: this.pagingSize, + page: this.curPage - 1, + }); + } // 合并计算出来的参数和固定参数,以计算参数为准 const tempParams = mergeDeepLeft(params, fixedParams); if (this.interfaceName) { @@ -290,6 +387,15 @@ export class PickerEditorController extends EditorController { context, tempParams, ); + + if (res.headers) { + if (res.headers['x-total']) { + this.total = Number(res.headers['x-total']); + } + if (res.headers['x-total-pages']) { + this.totalPages = Number(res.headers['x-total-pages']); + } + } return res as IHttpResponse; } throw new RuntimeModelError( diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 60e3ef06..e967364c 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -657,6 +657,7 @@ export default { cancel: 'Cancel', fullscreen: 'Fullscreen', minimize: 'Minimize', + loadMore: 'Load more', }, cascader: { ibizCascader: { diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index 51772b6f..6ad49fbd 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -613,6 +613,7 @@ export default { cancel: '取消', fullscreen: '全屏', minimize: '最小化', + loadMore: '加载更多', }, cascader: { ibizCascader: { -- Gitee From 57d724a95e352715d626d76919bcac30441962c4 Mon Sep 17 00:00:00 2001 From: lijisanxiong <1518062161@qq.com> Date: Fri, 26 Sep 2025 20:08:45 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=80=89=E6=8B=A9=E6=BF=80=E6=B4=BB=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E5=9B=9E=E6=98=BE=E9=80=BB=E8=BE=91=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/data-picker/ibiz-picker/ibiz-picker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx index e24e5fd8..363317b0 100644 --- a/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx +++ b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx @@ -526,7 +526,7 @@ export const IBizPicker = defineComponent({ const panel = this.c.deACMode?.itemLayoutPanel; const { context, params } = this.c; let selected = - item[this.c.textName] || item.srfmajortext === this.curValue; + (item[this.c.textName] || item.srfmajortext) === this.curValue; if (this.c.valueItem) { selected = (item[this.c.keyName] || item.srfkey) === this.data[this.c.valueItem]; -- Gitee From 1759484f6412a3ffe754a31d8033d407a7165599 Mon Sep 17 00:00:00 2001 From: lijisanxiong <1518062161@qq.com> Date: Fri, 26 Sep 2025 20:08:59 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat=EF=BC=9A=E6=9B=B4=E6=96=B0CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a03adf71..c38b1390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ - 表单分页计数器添加data-value属性,可通过data-value属性设置0值隐藏 - 新增文本框编辑器参数autoquestion(AI历史数据最后一个项是用户消息时是否自动提问,默认开启)和autofill(AI回答完成之后是否触发回填,默认关闭) +- 数据选择编辑器支持自填模式配置的加载更多、滚动加载 ### Fixed - 修复菜单图标样式显示不正确异常 +- 修复数据选择激活数据的回显逻辑计算异常 ## [0.7.41-alpha.27] - 2025-09-19 -- Gitee