From 8fdb4ba695fabbabc6cfbbe20f12b4aa2e23f796 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Sat, 8 Nov 2025 14:49:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=AF=BC=E8=88=AA=E8=A7=86=E5=9B=BE=E7=9A=84=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E5=88=B7=E6=96=B0=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view-engine/mob-tab-exp-view.engine.ts | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/view-engine/mob-tab-exp-view.engine.ts b/src/view-engine/mob-tab-exp-view.engine.ts index eee7f7b507c..c55370ce429 100644 --- a/src/view-engine/mob-tab-exp-view.engine.ts +++ b/src/view-engine/mob-tab-exp-view.engine.ts @@ -7,6 +7,8 @@ import { ITabExpViewState, ViewEngineBase, calcDeCodeNameById, + IApiMDViewCall, + SysUIActionTag, } from '@ibiz-template/runtime'; import { IAppDataEntity, @@ -197,6 +199,28 @@ export class MobTabExpViewEngine extends ViewEngineBase { await this.loadEntityData(); } + async call( + key: keyof IApiMDViewCall, + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + _args: any, + ): Promise { + if (key === SysUIActionTag.REFRESH) { + await this.refresh(); + return null; + } + } + + /** + * 视图刷新 + * + * @protected + * @return {*} {Promise} + * @memberof MobTabExpViewEngine + */ + protected async refresh(): Promise { + this.tabExpPanel.refresh(); + } + /** * 加载实体数据 * -- Gitee From 83a92a72a101fdc5be0d110e4209e702f156b13d Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Sat, 8 Nov 2025 14:50:43 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat=EF=BC=9A=E8=B0=83=E6=95=B4=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=AF=BC=E8=88=AA=E8=A7=86=E5=9B=BE=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=EF=BC=8C=E9=80=82=E9=85=8D=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E9=A2=84=E7=BD=AE=E6=A8=A1=E5=9E=8B=E5=B8=83=E5=B1=80=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view-engine/mob-tab-exp-view.engine.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/view-engine/mob-tab-exp-view.engine.ts b/src/view-engine/mob-tab-exp-view.engine.ts index c55370ce429..786125e9274 100644 --- a/src/view-engine/mob-tab-exp-view.engine.ts +++ b/src/view-engine/mob-tab-exp-view.engine.ts @@ -13,6 +13,7 @@ import { import { IAppDataEntity, IAppDETabExplorerView, + ILayout, IPanelContainer, IPanelItem, } from '@ibiz/model-core'; @@ -111,11 +112,20 @@ export class MobTabExpViewEngine extends ViewEngineBase { if (!tabexppanel) { return; } + // 左侧及右侧布局模式下需调整tabexppanel的flex布局, 高度需撑满 + if (['view_tabexppanel_left', 'view_tabexppanel_right'].includes(name)) { + tabexppanel.layoutPos = { + ...tabexppanel.layoutPos, + grow: 1, + } as ILayout; + } // 实际分页面板容器 const layoutContainer = findPanelItem(name); if (!layoutContainer) { return; } + // 删除原本的分页导航面板模型 + findPanelItemsAndDelete(['tabexppanel']); (layoutContainer as IPanelContainer).panelItems = [tabexppanel]; }; const { tabLayout } = this.view.model as IAppDETabExplorerView; @@ -151,13 +161,10 @@ export class MobTabExpViewEngine extends ViewEngineBase { deleteItems.splice(0, 1); break; } - // 非流式布局时进行额外处理 - if (tabLayout !== 'FLOW' && tabLayout !== 'FLOW_NOHEADER') { - // 设置分页导航面板布局 - setTabExpModelLayout(containerName); - // 删除多余布局模型 - findPanelItemsAndDelete(deleteItems); - } + // 设置分页导航面板布局 + setTabExpModelLayout(containerName); + // 删除多余布局模型 + findPanelItemsAndDelete(deleteItems); } /** -- Gitee From b0ce3ecc4516d158d5ccbc3e196c9c9f6e1d5133 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Sat, 8 Nov 2025 14:52:19 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E4=BF=A1=E6=81=AF=E9=A1=B9=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E9=80=82=E9=85=8D=E5=9B=BE=E6=A0=87=E3=80=81=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E4=B8=8E=E8=AE=A1=E6=95=B0=E5=99=A8=E7=BB=84=E5=90=88=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/index.ts | 2 + src/common/info-item/info-item.scss | 76 +++++++++++++++++++++++ src/common/info-item/info-item.tsx | 96 +++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 src/common/info-item/info-item.scss create mode 100644 src/common/info-item/info-item.tsx diff --git a/src/common/index.ts b/src/common/index.ts index eddffebdf5d..8ba5c534ae9 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -33,6 +33,7 @@ import { IBizMdAdvanedSearchfrom } from './md-advaned-searchform/md-advaned-sear import { IBizAddBtn } from './add-btn/add-btn'; import { IBizAddMore } from './add-more/add-more'; import { FloatButton } from './float-button/float-button'; +import { IBizInfoItem } from './info-item/info-item'; export * from './col/col'; export * from './row/row'; @@ -46,6 +47,7 @@ export const IBizCommonComponents = { install: (v: App): void => { v.component(IBizAddBtn.name!, IBizAddBtn); v.component(FloatButton.name!, FloatButton); + v.component(IBizInfoItem.name!, IBizInfoItem); v.component(IBizAddMore.name!, IBizAddMore); v.component(IBizMdAdvanedSearchfrom.name!, IBizMdAdvanedSearchfrom); v.component(IBizSplit.name!, IBizSplit); diff --git a/src/common/info-item/info-item.scss b/src/common/info-item/info-item.scss new file mode 100644 index 00000000000..a26df956cc9 --- /dev/null +++ b/src/common/info-item/info-item.scss @@ -0,0 +1,76 @@ +$info-item: ( + // Width/Height + height-icon-line-height: 1em, + height-label-line-height: 1em, + // Spacing + spacing-label-margin: 0 0 0 getCssVar('spacing', 'extra-tight'), + spacing-badge-margin: 0 0 0 getCssVar('spacing', 'tight'), + spacing-vertical-icon-wrapper-margin: 0 0 getCssVar('spacing', 'extra-tight') + 0, + // Font + font-icon-font-size: inherit, + font-label-font-size: inherit, + // Other + icon-size: 1em, + position-badge-top: 0px, + position-badge-left-offset: getCssVar('spacing', 'extra-tight'), + position-badge-left: calc( + 100% - #{getCssVar(info-item, position-badge-left-offset)} + ), + transform-badge: translateY(-50%) +); + +// 组件布局样式,字体与图标大小继承上层组件文本大小 +@include b(info-item) { + @include set-component-css-var('info-item', $info-item); + + position: relative; + display: flex; + align-items: center; + @include e('icon') { + &.#{bem(icon)}, + &.#{bem(icon)} svg, + &.#{bem(icon)} img { + display: inline-flex; + align-items: center; + width: getCssVar(info-item, icon-size); + height: getCssVar(info-item, icon-size); + font-size: getCssVar(info-item, font-icon-font-size); + line-height: getCssVar(info-item, height-icon-line-height); + } + &.#{bem(icon)} + .#{bem(info-item, label)} { + margin: getCssVar(info-item, spacing-label-margin); + } + } + + @include e('label') { + font-size: getCssVar(info-item, font-label-font-size); + line-height: getCssVar(info-item, height-label-line-height); + & + .#{bem(info-item, badge)} { + margin: getCssVar(info-item, spacing-badge-margin); + } + } + + @include when('badge-float') { + @include e('badge') { + position: absolute; + top: getCssVar(info-item, position-badge-top); + left: getCssVar(info-item, position-badge-left); + margin: 0; + transform: getCssVar(info-item, transform-badge); + } + } + + // 垂直布局样式 + @include when('vertical') { + flex-direction: column; + @include e('icon-wrapper') { + &:has(.#{bem(icon)}) { + position: relative; + display: flex; + align-items: center; + margin: getCssVar(info-item, spacing-vertical-icon-wrapper-margin); + } + } + } +} diff --git a/src/common/info-item/info-item.tsx b/src/common/info-item/info-item.tsx new file mode 100644 index 00000000000..60cd8b5ae6c --- /dev/null +++ b/src/common/info-item/info-item.tsx @@ -0,0 +1,96 @@ +import { defineComponent, PropType, VNode } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { isNil } from 'ramda'; +import { ISysImage } from '@ibiz/model-core'; +import './info-item.scss'; + +/** + * @description 通用信息项组件,用于展示「图标 + 文本 + 计数器」组合结构 + */ +export const IBizInfoItem = defineComponent({ + name: 'IBizInfoItem', + props: { + /** + * @description 文本值 + */ + label: { + type: String, + default: '', + }, + /** + * @description 计数器数值 + */ + badge: { + type: Number, + default: null, + }, + /** + * @description 图标资源信息 + */ + icon: { + type: Object as PropType, + }, + /** + * @description 布局模式,vertical为垂直布局,horizontal为水平布局 + */ + layoutMode: { + type: String as PropType<'vertical' | 'horizontal'>, + default: 'horizontal', + }, + /** + * @description 计数器是否浮动显示 + */ + badgeFloat: { + type: Boolean, + default: true, + }, + }, + setup(props) { + const ns = useNamespace('info-item'); + + const renderBadge = (): VNode | void => { + if (!isNil(props.badge)) { + return ; + } + }; + + const renderIcon = (): VNode | void => { + if (!isNil(props.icon)) { + return ; + } + }; + const renderLabel = (): VNode | void => { + if (!isNil(props.label)) { + return {props.label}; + } + }; + + const renderIconWarapper = (): VNode | void => { + return ( +
+ {renderIcon()} + {renderBadge()} +
+ ); + }; + + return { ns, renderBadge, renderIcon, renderLabel, renderIconWarapper }; + }, + render() { + let content = [this.renderIcon(), this.renderLabel(), this.renderBadge()]; + if (this.layoutMode === 'vertical') { + content = [this.renderIconWarapper(), this.renderLabel()]; + } + return ( +
+ {content} +
+ ); + }, +}); -- Gitee From 15d3bb7e226be76912fb5f72a0b1c2fe40bfe6ec Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Sat, 8 Nov 2025 14:52:51 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=AF=BC=E8=88=AA=E9=9D=A2=E6=9D=BF=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E5=8C=85=E5=90=AB=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E3=80=81=E4=BE=A7=E8=BE=B9=E6=A0=8F=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E3=80=81=E6=B5=81=E5=B8=83=E5=B1=80=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tab-exp-panel/layout-render-util.tsx | 90 +++++++++++++++++++ .../tab-default-layout.scss | 39 ++++++++ .../tab-default-layout/tab-default-layout.tsx | 73 +++++++++++++++ .../tab-flow-layout/tab-flow-layout.scss | 46 ++++++++++ .../tab-flow-layout/tab-flow-layout.tsx | 55 ++++++++++++ .../tab-sidebar-layout.scss | 72 +++++++++++++++ .../tab-sidebar-layout/tab-sidebar-layout.tsx | 62 +++++++++++++ 7 files changed, 437 insertions(+) create mode 100644 src/control/tab-exp-panel/layout-render-util.tsx create mode 100644 src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss create mode 100644 src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx create mode 100644 src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss create mode 100644 src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx create mode 100644 src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss create mode 100644 src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx diff --git a/src/control/tab-exp-panel/layout-render-util.tsx b/src/control/tab-exp-panel/layout-render-util.tsx new file mode 100644 index 00000000000..f1ac1e1ffbb --- /dev/null +++ b/src/control/tab-exp-panel/layout-render-util.tsx @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { Namespace } from '@ibiz-template/core'; +import { IApiTabExpPanelPagesState } from '@ibiz-template/runtime'; +import { PropType } from 'vue'; + +export type LayoutMode = + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'flow' + | 'flow_noheader'; + +interface ILayoutProps { + // 分页绘制数据集合 + tabPages: IApiTabExpPanelPagesState[]; + // 计数器数据 + counterData: IData; + // 分页标识激活名称 + activeName: string; + // 布局模式 + layoutMode: LayoutMode; +} + +/** + * 布局组件通用props + * + * @export + * @return {*} {IData} + */ +export const layoutProps = { + // 分页绘制数据集合 + tabPages: { + type: Array as PropType, + default: () => [], + }, + // 计数器数据 + counterData: { + type: Object as PropType, + default: () => ({}), + }, + // 分页标识激活名称 + activeName: { + type: String, + default: '', + }, + // 布局模式 + layoutMode: { + type: String as PropType, + default: 'top', + }, +}; + +/** + * 布局组件通用emits + * + * @export + * @return {*} {IData} + */ +export const layoutEmits = { + /** + * @description 分页标识变更事件 + */ + tabChange: (_tabTag: string) => true, +}; + +/** + * 布局组件通用方法 + * + * @export + * @param {ILayoutProps} props + */ +export function useLayoutRender( + props: ILayoutProps, + emit: (event: 'tabChange', _tabTag: string) => void, + _ns: Namespace, +): { + getCounterValue: (page: IApiTabExpPanelPagesState) => string | number | null; + onTabChange: (_tabTag: string) => void; +} { + // 获取计数器值 + const getCounterValue = (page: IApiTabExpPanelPagesState) => { + return page.counterId ? props.counterData[page.counterId] : null; + }; + + const onTabChange = (_tabTag: string) => { + emit('tabChange', _tabTag); + }; + return { getCounterValue, onTabChange }; +} diff --git a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss b/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss new file mode 100644 index 00000000000..b7c94788947 --- /dev/null +++ b/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss @@ -0,0 +1,39 @@ +$tab-default-layout: ( + // Color + color-item-text: getCssVar('control-tabexppanel', 'color-tab-text'), + color-border: getCssVar('color', 'border'), + // Width/Height + height: rem(50px), + // Font + font-tab-font-size: getCssVar('control-tabexppanel', 'font-tab-font-size') +); + +@include b(tab-default-layout) { + @include set-component-css-var('tab-default-layout', $tab-default-layout); + + @include e('tab-item') { + color: getCssVar(tab-default-layout, color-item-text); + font-size: getCssVar(tab-default-layout, font-tab-font-size); + } + + @include when('top') { + .van-tabs--line .van-tabs__wrap { + height: getCssVar(tab-default-layout, height); + } + + .van-tab__text { + overflow: unset; + } + } + + @include when('bottom') { + .van-tabbar { + height: getCssVar(tab-default-layout, height); + border-top: 1px solid getCssVar(tab-default-layout, color-border); + } + + .van-tabs__line { + display: none; + } + } +} diff --git a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx b/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx new file mode 100644 index 00000000000..fb05f887932 --- /dev/null +++ b/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx @@ -0,0 +1,73 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { + layoutEmits, + layoutProps, + useLayoutRender, +} from '../layout-render-util'; +import './tab-default-layout.scss'; + +export const TabDefaultLayout = defineComponent({ + name: 'IBizTabDefaultLayout', + props: layoutProps, + emits: layoutEmits, + setup(props, { emit }) { + const ns = useNamespace('tab-default-layout'); + + const { getCounterValue, onTabChange } = useLayoutRender(props, emit, ns); + + return { ns, getCounterValue, onTabChange }; + }, + render() { + let content = ( + + {this.tabPages.map(tab => { + return ( + + {{ + title: () => ( + + ), + }} + + ); + })} + + ); + if (this.layoutMode === 'bottom') { + content = ( + + {this.tabPages.map(tab => { + return ( + + {{ + default: () => ( + + ), + }} + + ); + })} + + ); + } + return ( +
+ {content} +
+ ); + }, +}); diff --git a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss b/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss new file mode 100644 index 00000000000..0807cb8554a --- /dev/null +++ b/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss @@ -0,0 +1,46 @@ +$tab-flow-layout: ( + // Color + color-group-header-text: getCssVar('control-tabexppanel', 'color-tab-text'), + color-group-header-bg: getCssVar(color, bg, 0), + color-group-noheader-bg: getCssVar(color, bg, 0), + // Width/Height + height-group-header-content-min-height: + getCssVar('height-control', 'default'), + // Spacing + spacing-group-header-padding: getCssVar('spacing', 'tight') + getCssVar('spacing', 'base'), + spacing-group-header-ph-padding: getCssVar('spacing', 'tight'), + // Font + font-group-header-font-size: getCssVar( + 'control-tabexppanel', + 'font-tab-font-size' + ) +); +@include b(tab-flow-layout) { + @include set-component-css-var('tab-flow-layout', $tab-flow-layout); + + @include e('group-header') { + padding: getCssVar(tab-flow-layout, spacing-group-header-padding); + font-size: getCssVar(tab-flow-layout, font-group-header-font-size); + color: getCssVar(tab-flow-layout, color-group-header-text); + background-color: getCssVar(tab-flow-layout, color-group-header-bg); + + @include m('content') { + min-height: getCssVar( + tab-flow-layout, + height-group-header-content-min-height + ); + } + } + + .van-index-anchor { + padding: 0; + } + + @include when('flow_noheader') { + @include e('group-header-ph') { + padding: getCssVar(tab-flow-layout, spacing-group-header-ph-padding); + background-color: getCssVar(tab-flow-layout, color-group-noheader-bg); + } + } +} diff --git a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx b/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx new file mode 100644 index 00000000000..aedfa931748 --- /dev/null +++ b/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx @@ -0,0 +1,55 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { + layoutEmits, + layoutProps, + useLayoutRender, +} from '../layout-render-util'; +import './tab-flow-layout.scss'; + +export const TabFlowLayout = defineComponent({ + name: 'IBizTabFlowLayout', + props: layoutProps, + emits: layoutEmits, + setup(props, { emit }) { + const ns = useNamespace('tab-flow-layout'); + const { getCounterValue } = useLayoutRender(props, emit, ns); + + return { ns, getCounterValue }; + }, + render() { + const indexList = this.tabPages.map(x => x.caption); + return ( + + {this.tabPages.map(tab => { + return ( +
+ + {this.layoutMode === 'flow_noheader' ? ( +
+ ) : ( +
+ +
+ )} +
+
+ {this.$slots.groupContent?.(tab)} +
+
+ ); + })} +
+ ); + }, +}); diff --git a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss b/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss new file mode 100644 index 00000000000..5c03aae4af7 --- /dev/null +++ b/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss @@ -0,0 +1,72 @@ +$tab-sidebar-layout: ( + // Color + color-bg: getCssVar(color, bg, 0), + color-item-text: getCssVar('control-tabexppanel', 'color-tab-text'), + color-item-bg: getCssVar(color, bg, 0), + color-bg-active: getCssVar(color, bg, 2), + // Width/Height + width: rem(90px), + height: 100%, + height-tab-item: getCssVar('height-control', 'default'), + width-badge-min-width: rem(20px), + // Spacing + spacing-padding: getCssVar('spacing', 'tight') 0, + spacing-sidebar-item-padding: getCssVar('spacing', 'tight') + getCssVar('spacing', 'tight'), + // Font + font-tab-font-size: getCssVar('control-tabexppanel', 'font-tab-font-size') +); +@include b(tab-sidebar-layout) { + @include set-component-css-var('tab-sidebar-layout', $tab-sidebar-layout); + + width: getCssVar(tab-sidebar-layout, width); + height: getCssVar(tab-sidebar-layout, height); + padding: getCssVar(tab-sidebar-layout, spacing-padding); + background-color: getCssVar(tab-sidebar-layout, color-bg); + + @include e('tab-item') { + display: flex; + align-items: center; + width: 100%; + min-height: getCssVar(tab-sidebar-layout, height-tab-item); + font-size: getCssVar(tab-sidebar-layout, font-tab-font-size); + color: getCssVar(tab-sidebar-layout, color-item-text); + .#{bem(info-item)} { + display: inline-flex; + max-width: 100%; + } + .#{bem(info-item, label)} { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + @include when('long-label') { + &:has(.#{bem(info-item, badge)}) { + max-width: calc( + 100% - #{getCssVar(tab-sidebar-layout, width-badge-min-width)} + ); + } + } + } + + .van-sidebar-item { + display: flex; + align-items: center; + padding: getCssVar(tab-sidebar-layout, spacing-sidebar-item-padding); + overflow: unset; + + &:not(.van-sidebar-item--select) { + background-color: getCssVar(tab-sidebar-layout, color-item-bg); + } + + &.van-sidebar-item--select { + background-color: getCssVar(tab-sidebar-layout, color-bg-active); + } + } + + .van-sidebar-item__text { + width: 100%; + height: 100%; + } +} diff --git a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx b/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx new file mode 100644 index 00000000000..75b239dec62 --- /dev/null +++ b/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx @@ -0,0 +1,62 @@ +import { defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { + layoutEmits, + layoutProps, + useLayoutRender, +} from '../layout-render-util'; +import './tab-sidebar-layout.scss'; + +export const TabSidebarLayout = defineComponent({ + name: 'IBizTabSidebarLayout', + props: layoutProps, + emits: layoutEmits, + setup(props, { emit }) { + const ns = useNamespace('tab-sidebar-layout'); + + // 激活下标 + const activeIndex = ref(0); + + const { getCounterValue, onTabChange } = useLayoutRender(props, emit, ns); + const onSidebarChange = (index: number) => { + activeIndex.value = index; + const tabTag = props.tabPages[index].tabTag; + onTabChange(tabTag); + }; + + return { ns, activeIndex, getCounterValue, onTabChange, onSidebarChange }; + }, + render() { + return ( + + {this.tabPages.map(tab => { + const isLongLabel = tab.caption.length > 3; + return ( + + {{ + title: () => ( +
+ +
+ ), + }} +
+ ); + })} +
+ ); + }, +}); -- Gitee From 338351644cef287c53defa5299ccaa9c2325de44 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Sat, 8 Nov 2025 14:53:20 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat=EF=BC=9A=E4=BC=98=E5=8C=96=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=AF=BC=E8=88=AA=E9=9D=A2=E6=9D=BF=E7=9A=84=E7=BB=98?= =?UTF-8?q?=E5=88=B6=E7=BB=93=E6=9E=84=EF=BC=8C=E5=B9=B6=E4=B8=94=E5=B0=86?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8=E9=80=BB=E8=BE=91=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E8=87=B3=E6=8E=A7=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/tab-exp-panel/tab-exp-panel.scss | 37 +---- src/control/tab-exp-panel/tab-exp-panel.tsx | 140 ++++++------------- 2 files changed, 50 insertions(+), 127 deletions(-) diff --git a/src/control/tab-exp-panel/tab-exp-panel.scss b/src/control/tab-exp-panel/tab-exp-panel.scss index e223643ff7b..adc33184a4b 100644 --- a/src/control/tab-exp-panel/tab-exp-panel.scss +++ b/src/control/tab-exp-panel/tab-exp-panel.scss @@ -1,35 +1,12 @@ $control-tabexppanel: ( - 'side-padding': getCssVar(spacing, base), + // Color + color-tab-text: getCssVar('color', 'text', 0), + // Font + font-tab-font-size: getCssVar('font-size', 'regular') ); - -$control-tabexppanel-flow: ( - 'header-padding': getCssVar(spacing, tight) getCssVar(spacing, base), - 'font-size': getCssVar(font-size, regular), - 'line-height': getCssVar(height-control, small), - 'header-color': getCssVar(color, text, 2), - 'header-bg': getCssVar(color, bg, 0), -); - -@include b(control-tabexppanel-tab-item-header) { - @include set-component-css-var('control-tabexppanel-flow', $control-tabexppanel-flow); - padding: getCssVar(control-tabexppanel-flow, header-padding); - font-size: getCssVar(control-tabexppanel-flow, font-size); - line-height: getCssVar(control-tabexppanel-flow, line-height); - color: getCssVar(control-tabexppanel-flow, header-color); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - background: getCssVar(control-tabexppanel-flow, header-bg); -} - @include b(control-tabexppanel) { @include set-component-css-var('control-tabexppanel', $control-tabexppanel); - @include e(sidebar) { - @include m(left) { - margin-left: getCssVar(control-tabexppanel, side-padding); - } - @include m(right) { - margin-right: getCssVar(control-tabexppanel, side-padding); - } - } + + width: 100%; + height: 100%; } diff --git a/src/control/tab-exp-panel/tab-exp-panel.tsx b/src/control/tab-exp-panel/tab-exp-panel.tsx index 7d53f252798..df7f04d0021 100644 --- a/src/control/tab-exp-panel/tab-exp-panel.tsx +++ b/src/control/tab-exp-panel/tab-exp-panel.tsx @@ -1,15 +1,20 @@ import { useControlController, useNamespace } from '@ibiz-template/vue3-util'; -import { defineComponent, h, PropType, Ref, ref, resolveComponent } from 'vue'; +import { defineComponent, h, PropType, resolveComponent } from 'vue'; import { IAppDETabExplorerView, IDETabViewPanel, ITabExpPanel, } from '@ibiz/model-core'; import { + IApiTabExpPanelPagesState, IControlProvider, TabExpPanelController, } from '@ibiz-template/runtime'; import './tab-exp-panel.scss'; +import { TabFlowLayout } from './tab-flow-layout/tab-flow-layout'; +import { TabSidebarLayout } from './tab-sidebar-layout/tab-sidebar-layout'; +import { TabDefaultLayout } from './tab-default-layout/tab-default-layout'; +import { LayoutMode } from './layout-render-util'; export const TabExpPanelControl = defineComponent({ name: 'IBizTabExpPanelControl', @@ -45,117 +50,58 @@ export const TabExpPanelControl = defineComponent({ // 视图模型 const model = c.view?.model as IAppDETabExplorerView; // 布局模式 - const tabPosition = model?.tabLayout?.toLowerCase() || 'top'; - - const counterData: Ref = ref({}); - - // 激活下标,侧边栏使用 - const activeIndex = ref(0); - - const fn = (counter: IData) => { - counterData.value = counter; - }; - - c.evt.on('onCreated', () => { - if (c.counter) { - c.counter.onChange(fn, true); - } - }); + const layoutMode = model?.tabLayout?.toLowerCase() || 'top'; const onTabChange = (value: string) => { c.state.activeName = value; c.handleTabChange(); }; - const onSidebarChange = (index: number) => { - activeIndex.value = index; - const value = c.state.tabPages[index].tabTag; - c.state.activeName = value; - c.handleTabChange(); - }; - return { c, ns, - activeIndex, - tabPosition, - counterData, + layoutMode, onTabChange, - onSidebarChange, }; }, render() { - const { isCreated, tabPages } = this.c.state; - if (this.tabPosition === 'flow' || this.tabPosition === 'flow_noheader') { - if (!isCreated) { - return; - } - return ( -
- {tabPages.map(page => { - const target = this.c.model.controls?.find( - tab => tab.id === page.tabTag, - ) as IDETabViewPanel; - return ( -
-
{page.caption}
-
- {h(resolveComponent('IBizViewShell'), { - context: this.context, - params: this.params, - viewId: target?.embeddedAppDEViewId, - })} -
-
- ); - })} -
- ); + const { isCreated, tabPages, counterData, activeName } = this.c.state; + if (!isCreated) { + return; } - if (['left', 'right'].includes(this.tabPosition)) { - return ( - - {tabPages.map(page => { - return ( - - ); - })} - + const attrs = { + tabPages, + counterData, + activeName, + layoutMode: this.layoutMode as LayoutMode, + onTabChange: this.onTabChange, + }; + + let content = ; + + if (this.layoutMode === 'flow' || this.layoutMode === 'flow_noheader') { + content = ( + + {{ + groupContent: (page: IApiTabExpPanelPagesState) => { + const target = this.c.model.controls?.find( + tab => tab.id === page.tabTag, + ) as IDETabViewPanel; + return h(resolveComponent('IBizViewShell'), { + context: this.context, + params: this.params, + viewId: target?.embeddedAppDEViewId, + }); + }, + }} + ); } - return ( - isCreated && ( - - {tabPages.map(page => { - return ( - - ); - })} - - ) - ); + + if (['left', 'right'].includes(this.layoutMode)) { + content = ; + } + + return
{content}
; }, }); -- Gitee