diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e76b0401c21bf301b0e9028522c6ef4d92dff5c..86f70fffbb48a1ab526b65b4e1a822eaa7627f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ ## [Unreleased] +### Added + +- 新增表格列过滤功能 +- 新增表格,列表支持行明细内置导航模式 +- appUtil新增获取应用上下文 + +### Fixed + +- 修复发送appdata请求未携带应用上下文问题 + ## [0.7.40-alpha.21] - 2025-05-28 ### Added diff --git a/src/common/control-navigation/control-navigation.tsx b/src/common/control-navigation/control-navigation.tsx index 5ab69f80a686cd9a7a9b9bc6716bce50ff44b89f..d46d30e4ffff4b28223c1acfd2a794202644947d 100644 --- a/src/common/control-navigation/control-navigation.tsx +++ b/src/common/control-navigation/control-navigation.tsx @@ -6,11 +6,11 @@ import { h, ref, Ref, + watch, PropType, + onMounted, defineComponent, resolveComponent, - watch, - onMounted, } from 'vue'; import { getNavigationProvider } from './provider'; import './control-navigation.scss'; diff --git a/src/common/control-navigation/provider/navigation-base.provider.ts b/src/common/control-navigation/provider/navigation-base.provider.ts index 578db01e1eadb3cc105b893704a32030a0370ba7..8d9165c6012ab5aa1a38b43bc1545b51e2d2b78e 100644 --- a/src/common/control-navigation/provider/navigation-base.provider.ts +++ b/src/common/control-navigation/provider/navigation-base.provider.ts @@ -1,10 +1,10 @@ import { Ref, ref } from 'vue'; import { - IMDControlEvent, INavViewMsg, + calcNavParams, + IMDControlEvent, MDControlController, calcDeCodeNameById, - calcNavParams, } from '@ibiz-template/runtime'; import { IDER1N, IMDControl, INavigatable } from '@ibiz/model-core'; diff --git a/src/control/grid/grid-column/grid-column-header/grid-column-header.scss b/src/control/grid/grid-column/grid-column-header/grid-column-header.scss index 3863f9979aadd6b8dfe863d505dce1e828b3cdbf..f48e25a2279b64615baf6e949bd2c48876bfde14 100644 --- a/src/control/grid/grid-column/grid-column-header/grid-column-header.scss +++ b/src/control/grid/grid-column/grid-column-header/grid-column-header.scss @@ -7,4 +7,28 @@ gap: getCssVar(spacing, extra-tight); align-items: center; } + + @include e(filter) { + @include m(icon) { + height: 23px; + visibility: visible; + color: getCssVar(color, text, 3); + margin-left: getCssVar(spacing, extra, tight); + @include when(active) { + color: getCssVar(color, primary); + } + } + } + + @include e(popover) { + @include m(editor) { + padding-bottom: getCssVar(spacing, tight); + border-bottom: 1px solid getCssVar(color, border); + } + @include m(bottom) { + display: flex; + align-items: center; + float: right; + } + } } diff --git a/src/control/grid/grid-column/grid-column-header/grid-column-header.tsx b/src/control/grid/grid-column/grid-column-header/grid-column-header.tsx index 6fd74ef14cb0ac10c25917c8d6f8abe96f1b1ad1..23c8c7259bbcb19b4d6b71604b8d0e6eea4ec157 100644 --- a/src/control/grid/grid-column/grid-column-header/grid-column-header.tsx +++ b/src/control/grid/grid-column/grid-column-header/grid-column-header.tsx @@ -1,6 +1,15 @@ -import { PropType, defineComponent } from 'vue'; +import { + h, + ref, + PropType, + computed, + onUnmounted, + defineComponent, + resolveComponent, +} from 'vue'; import { GridColumnController } from '@ibiz-template/runtime'; -import { useNamespace } from '@ibiz-template/vue3-util'; +import { useClickOutside, useNamespace } from '@ibiz-template/vue3-util'; +import { OnClickOutsideResult, eventPath } from '@ibiz-template/core'; import { IDEGridColumn } from '@ibiz/model-core'; import './grid-column-header.scss'; @@ -19,10 +28,101 @@ export const GridColumnHeader = defineComponent({ setup(props) { const ns = useNamespace('grid-column-header'); const c = props.controller; + const content = ref(); + const visible = ref(false); + const curValue = ref(); + + const filterValue = computed(() => { + const filterName = (c.model as IData).filterEditor?.id?.toLowerCase(); + return filterName ? c.grid.state.columnFilter[filterName] : undefined; + }); + + let funcs: OnClickOutsideResult | undefined; + + /** + * 显示 + * + * @return {*} {void} + */ + const onShow = (): void => { + if (funcs) return funcs.proceed(); + funcs = useClickOutside(content, evt => { + const classList: string[] = []; + eventPath(evt).forEach(e => { + if (e && (e as IData).classList) { + classList.push(...(e as IData).classList); + } + }); + if (classList.includes('el-popper')) return; + visible.value = false; + }); + }; + + /** + * 隐藏 + * + */ + const onHide = (): void => { + // 停止监听 + curValue.value = filterValue.value; + funcs?.pause(); + }; + + /** + * 过滤值改变 + * + * @param {unknown} val + * @param {string} [_name] + */ + const onFilterChange = (val: unknown, _name?: string): void => { + curValue.value = val; + }; + + /** + * 筛选 + * + */ + const onScreen = (): void => { + visible.value = false; + c.handleColumnScreen(curValue.value); + }; + + /** + * 重置 + * + */ + const onReset = (): void => { + curValue.value = undefined; + onScreen(); + }; + + /** + * 点击 + * + * @param {MouseEvent} e + */ + const onClick = (e: MouseEvent): void => { + e.stopPropagation(); + visible.value = true; + }; + + onUnmounted(() => { + funcs?.stop(); + }); return { c, ns, + visible, + content, + curValue, + filterValue, + onShow, + onHide, + onReset, + onClick, + onScreen, + onFilterChange, }; }, render() { @@ -37,6 +137,59 @@ export const GridColumnHeader = defineComponent({ {this.c.model.caption} + {this.c.filterEditorProvider && ( + + {{ + reference: () => { + return ( + + ); + }, + default: () => { + return ( +
+
+ {h( + resolveComponent( + this.c.filterEditorProvider!.gridEditor, + ), + { + data: {}, + autoFocus: true, + value: this.curValue, + onChange: this.onFilterChange, + controller: this.c.filterEditor, + }, + )} +
+
+ + {ibiz.i18n.t('app.search')} + + + {ibiz.i18n.t('app.reset')} + +
+
+ ); + }, + }} +
+ )} ); }, diff --git a/src/control/grid/grid/grid.tsx b/src/control/grid/grid/grid.tsx index 2ada6c957a0cec70a88b3da2b3317265f2fb5eec..d77e72e1666e3e3056ef85a0ec596f787ffd160b 100644 --- a/src/control/grid/grid/grid.tsx +++ b/src/control/grid/grid/grid.tsx @@ -2,22 +2,22 @@ import { useUIStore, useNamespace, + IBizCustomRender, useControlController, hasEmptyPanelRenderer, - IBizCustomRender, } from '@ibiz-template/vue3-util'; import { - defineComponent, - onUnmounted, - PropType, - VNode, - renderSlot, - VNodeArrayChildren, - computed, + h, ref, watch, + VNode, + PropType, + computed, + renderSlot, + onUnmounted, + defineComponent, resolveComponent, - h, + VNodeArrayChildren, } from 'vue'; import { IDEGrid, @@ -26,11 +26,11 @@ import { IUIActionGroupDetail, } from '@ibiz/model-core'; import { - GridController, - GridFieldColumnController, GridRowState, - IControlProvider, ScriptFactory, + GridController, + IControlProvider, + GridFieldColumnController, } from '@ibiz-template/runtime'; import { NOOP, showTitle } from '@ibiz-template/core'; import { createUUID } from 'qx-util'; @@ -38,9 +38,9 @@ import { isNotNil } from 'ramda'; import { IGridProps, useAppGridBase, - useGridHeaderStyle, - useGridDraggable, useITableEvent, + useGridDraggable, + useGridHeaderStyle, } from './grid-control.util'; import { useRowEditPopover } from '../row-edit-popover/use-row-edit-popover'; import { usePagination } from '../../../util'; @@ -498,6 +498,7 @@ export const GridControl = defineComponent({ ); }; + // 绘制表格列 const renderTableColumn = ( model: IDEGridColumn, @@ -551,6 +552,35 @@ export const GridControl = defineComponent({ ); }; + /** + * 绘制行明细 + * + * @return {*} + */ + const renderRowDetail = () => { + const { navAppViewId, navViewHeight } = c.model; + if (navAppViewId && c.state.showRowDetail) + return ( + + {{ + default: ({ row }: IData): VNode | null => { + const { context, params } = c.calcNavParams(row); + const style = { + height: navViewHeight ? `${navViewHeight}px` : 'auto', + }; + return h(resolveComponent('IBizViewShell'), { + style, + params, + context, + viewId: navAppViewId, + class: ns.b('row-detail-view'), + }); + }, + }} + + ); + }; + onUnmounted(() => { zIndex.decrement(); if (cleanup !== NOOP) cleanup(); @@ -597,32 +627,33 @@ export const GridControl = defineComponent({ return { c, ns, - sysCssName, tableRef, tableData, + sysCssName, + defaultSort, renderColumns, - renderTableColumn, - onDbRowClick, + headerCssVars, + infiniteScroll, + infiniteScrollKey, + isLodeMoreDisabled, onRowClick, - onSelectionChange, + spanMethod, onSortChange, + onDbRowClick, onPageChange, - onPageSizeChange, - onPageRefresh, - handleRowClassName, - handleHeaderCellClassName, renderNoData, summaryMethod, - spanMethod, headerDragend, renderPopover, - defaultSort, + onPageRefresh, + renderRowDetail, + onPageSizeChange, + renderTableColumn, + onSelectionChange, + handleRowClassName, renderBatchToolBar, - headerCssVars, renderDragIconColumn, - isLodeMoreDisabled, - infiniteScroll, - infiniteScrollKey, + handleHeaderCellClassName, }; }, render() { @@ -689,6 +720,7 @@ export const GridControl = defineComponent({ align='center' > ), + this.renderRowDetail(), this.renderColumns.map((model, index) => { return this.renderTableColumn(model, index); }), diff --git a/src/control/list/list.scss b/src/control/list/list.scss index badd2292bc644beda8b60d36a4cd53bca80df8a1..90f77d2b1b546dc75d98712238d9e0fbc960e545 100644 --- a/src/control/list/list.scss +++ b/src/control/list/list.scss @@ -10,11 +10,11 @@ $control-list: ( @include b(control-list-item) { flex-grow: 1; - cursor: pointer; min-height: getCssVar(control-list, item-height); padding: getCssVar(control-list, padding); font-weight: getCssVar(control-list, font-weight); color: getCssVar(control-list, text-color); + cursor: pointer; background-color: getCssVar(control-list, item-bg-color); } @@ -79,7 +79,7 @@ $control-list: ( border-bottom: 1px solid getCssVar(color, border); } } - .#{bem(control-list-scroll-item)}:last-of-type { + .#{bem(row-detail)}:last-of-type .#{bem(control-list-scroll-item)} { border-bottom: none; } } @@ -106,6 +106,13 @@ $control-list: ( margin-left: getCssVar(spacing, tight); } + @include e(icon) { + flex-shrink: 0; + margin-left: getCssVar(spacing, tight); + color: getCssVar(color, text-2); + cursor: pointer; + } + &:hover { background-color: getCssVar(control-list, hover-bg-color); } diff --git a/src/control/list/list.tsx b/src/control/list/list.tsx index 81b2df61556eeee096ffbc7b59f202caa604bef0..b46d3bccf953e792d8ea814d260f8729818e1cb7 100644 --- a/src/control/list/list.tsx +++ b/src/control/list/list.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-return-assign */ /* eslint-disable no-nested-ternary */ import { hasEmptyPanelRenderer, @@ -249,37 +250,82 @@ export const ListControl = defineComponent({ } }; + /** + * 绘制行明细 + * + * @param {IData} item + * @return {*} + */ + const renderRowDetail = (item: IData) => { + const { navAppViewId, navViewHeight } = c.model; + const { context, params } = c.calcNavParams(item); + const style = { + height: navViewHeight ? `${navViewHeight}px` : 'auto', + }; + return ( + + ); + }; + + const renderItem = (item: IData) => { + const cardStyle = ns.cssVarBlock({ + 'item-bg-color': `${item.bgcolor || ''}`, + 'text-color': `${item.fontcolor || ''}`, + 'hover-bg-color': `${item.hovercolor || ''}`, + 'active-bg-color': `${item.activecolor || ''}`, + }); + const panel = props.modelData.itemLayoutPanel; + return ( +
+ {c.model.controlStyle === 'EXTVIEW2' && !c.state.singleSelect && ( + toggleSelection(item)} + /> + )} + {c.model.navAppViewId && c.state.showRowDetail && ( + (item.__isExpand = !item.__isExpand)} + > + )} + {panel ? renderPanelItem(item, panel) : renderDefaultItem(item)} +
+ ); + }; + /** * 绘制列表项 * * @param {IData[]} items */ const renderListItems = (items: IData[]) => { - const panel = props.modelData.itemLayoutPanel; - + const { navAppViewId } = c.model; return items.map(item => { - const cardStyle = ns.cssVarBlock({ - 'item-bg-color': `${item.bgcolor || ''}`, - 'text-color': `${item.fontcolor || ''}`, - 'hover-bg-color': `${item.hovercolor || ''}`, - 'active-bg-color': `${item.activecolor || ''}`, - }); - return ( -
- {c.model.controlStyle === 'EXTVIEW2' && !c.state.singleSelect && ( - toggleSelection(item)} - /> - )} - {panel ? renderPanelItem(item, panel) : renderDefaultItem(item)} -
- ); + if (navAppViewId && c.state.showRowDetail) + return ( +
+ {renderItem(item)} + {item.__isExpand && renderRowDetail(item)} +
+ ); + return renderItem(item); }); }; diff --git a/src/panel-component/user-message/user-message.tsx b/src/panel-component/user-message/user-message.tsx index 8986733b7ec77156345f65e9975e5c7f3f38b5bd..d19bddae77c5877890ccc4c535819fbdfd3d6fa8 100644 --- a/src/panel-component/user-message/user-message.tsx +++ b/src/panel-component/user-message/user-message.tsx @@ -35,7 +35,6 @@ export const UserMessage = defineComponent({ setup(props) { const ns = useNamespace('user-message'); const c = props.controller; - const noticeController = ibiz.hub.notice; const showPopover = ref(false); @@ -72,7 +71,7 @@ export const UserMessage = defineComponent({ }; const verifyAuthentication = async () => { - const res = await ibiz.net.get('/appdata'); + const res = await ibiz.net.get('/appdata', ibiz.appUtil.getAppContext()); if (res.ok) { ibiz.appData = res.data; updateDevToolConfig(); diff --git a/src/util/app-util/app-util.ts b/src/util/app-util/app-util.ts index 896a1dcbbc2efcf433664daa0ee8d307976f57c4..3f9ecc5ad9574ca251a38df0ea1d71f2d6bba3d6 100644 --- a/src/util/app-util/app-util.ts +++ b/src/util/app-util/app-util.ts @@ -24,7 +24,7 @@ import { IPortalAsyncAction, } from '@ibiz-template/core'; import { AxiosProgressEvent } from 'axios'; -import { useUIStore } from '@ibiz-template/vue3-util'; +import { route2routePath, useUIStore } from '@ibiz-template/vue3-util'; import { calcAiToolbarItemsByAc } from '../ai-util/ai-util'; export class AppUtil implements IAppUtil { @@ -170,6 +170,17 @@ export class AppUtil implements IAppUtil { throw new Error('Method not implemented.'); } + /** + * 获取应用上下文 + * + * @return {*} {(IParams | undefined)} + * @memberof AppUtil + */ + getAppContext(): IParams | undefined { + const routePath = route2routePath(this.router.currentRoute.value); + return routePath.appContext; + } + /** * 校验密码 *