From 4b2190e344c55ae1975b2a38aca9966d42657d0b Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:14:16 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E5=B8=83=E5=B1=80=E9=80=9A=E7=94=A8=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=A0=8F=E4=BD=8D=E7=BD=AE=EF=BC=8C=E9=80=82?= =?UTF-8?q?=E9=85=8D=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=88=E4=B8=8A=E6=96=B9?= =?UTF-8?q?=E3=80=81=E4=B8=8B=E6=96=B9=E3=80=81=E5=B7=A6=E4=BE=A7=E3=80=81?= =?UTF-8?q?=E5=8F=B3=E4=BE=A7=E3=80=81=E6=B5=81=E5=B8=83=E5=B1=80=E3=80=81?= =?UTF-8?q?=E6=B5=81=E5=B8=83=E5=B1=80=EF=BC=88=E6=97=A0=E6=A0=87=E9=A2=98?= =?UTF-8?q?=EF=BC=89=E3=80=81=E4=B8=8A=E6=96=B9=EF=BC=88=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E5=88=97=E8=A1=A8=EF=BC=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/index.ts | 2 + .../tab-default-layout.scss | 37 +++++++++ .../tab-default-layout/tab-default-layout.tsx | 73 ++++++++++++++++ .../tab-dropdown-list-content.scss | 69 +++++++++++++++ .../tab-dropdown-list-content.tsx | 54 ++++++++++++ .../tab-layout/tab-dropdown/tab-dropdown.scss | 49 +++++++++++ .../tab-layout/tab-dropdown/tab-dropdown.tsx | 81 ++++++++++++++++++ .../tab-flow-layout/tab-flow-layout.scss | 41 +++++++++ .../tab-flow-layout/tab-flow-layout.tsx | 50 +++++++++++ src/common/tab-layout/tab-layout-util.tsx | 83 +++++++++++++++++++ src/common/tab-layout/tab-layout.scss | 13 +++ src/common/tab-layout/tab-layout.tsx | 53 ++++++++++++ .../tab-sidebar-layout.scss | 69 +++++++++++++++ .../tab-sidebar-layout/tab-sidebar-layout.tsx | 58 +++++++++++++ 14 files changed, 732 insertions(+) create mode 100644 src/common/tab-layout/tab-default-layout/tab-default-layout.scss create mode 100644 src/common/tab-layout/tab-default-layout/tab-default-layout.tsx create mode 100644 src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.scss create mode 100644 src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.tsx create mode 100644 src/common/tab-layout/tab-dropdown/tab-dropdown.scss create mode 100644 src/common/tab-layout/tab-dropdown/tab-dropdown.tsx create mode 100644 src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss create mode 100644 src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx create mode 100644 src/common/tab-layout/tab-layout-util.tsx create mode 100644 src/common/tab-layout/tab-layout.scss create mode 100644 src/common/tab-layout/tab-layout.tsx create mode 100644 src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss create mode 100644 src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx diff --git a/src/common/index.ts b/src/common/index.ts index 8ba5c534ae9..1ae808edad0 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -34,6 +34,7 @@ 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'; +import { IBizTabLayout } from './tab-layout/tab-layout'; export * from './col/col'; export * from './row/row'; @@ -78,6 +79,7 @@ export const IBizCommonComponents = { v.component(IBizSignaturePad.name!, IBizSignaturePad); v.component(ViewMessage.name!, ViewMessage); v.component(IBizMdSortSetting.name!, IBizMdSortSetting); + v.component(IBizTabLayout.name!, IBizTabLayout); }, }; diff --git a/src/common/tab-layout/tab-default-layout/tab-default-layout.scss b/src/common/tab-layout/tab-default-layout/tab-default-layout.scss new file mode 100644 index 00000000000..a5ed3f642a8 --- /dev/null +++ b/src/common/tab-layout/tab-default-layout/tab-default-layout.scss @@ -0,0 +1,37 @@ +$tab-default-layout: ( + // Color + color-border: getCssVar('color', 'border'), + // Width/Height + height: rem(50px), + // Font + font-tab-font-size: getCssVar('tab-layout', 'font-tab-font-size') +); + +@include b(tab-default-layout) { + @include set-component-css-var('tab-default-layout', $tab-default-layout); + + @include e('tab-item') { + 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/common/tab-layout/tab-default-layout/tab-default-layout.tsx b/src/common/tab-layout/tab-default-layout/tab-default-layout.tsx new file mode 100644 index 00000000000..32934890e72 --- /dev/null +++ b/src/common/tab-layout/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 '../tab-layout-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 { onTabChange } = useLayoutRender(emit); + + return { ns, onTabChange }; + }, + render() { + let content = ( + + {this.tabPages.map(tab => { + if (tab.isHidden) return; + + return ( + + {{ + title: () => ( + + ), + }} + + ); + })} + + ); + if (this.layoutMode === 'bottom') { + content = ( + + {this.tabPages.map(tab => { + if (tab.isHidden) return; + + return ( + + {{ + default: () => ( + + ), + }} + + ); + })} + + ); + } + return ( +
+ {content} +
+ ); + }, +}); diff --git a/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.scss b/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.scss new file mode 100644 index 00000000000..465fb159856 --- /dev/null +++ b/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.scss @@ -0,0 +1,69 @@ +$tab-dropdown-list-content: ( + // Color + color-item-text: getCssVar('tab-dropdown', 'color-text'), + color-item-text-active: getCssVar('tab-dropdown', 'color-text-active'), + color-wrapper-bg: getCssVar('color', 'bg', 1), + // Width/Height + width: rem(90px), + height-item: getCssVar('height-control', 'default'), + // Spacing + spacing-wrapper-padding: getCssVar('spacing', 'extra-tight') 0, + spacing-item-padding: 0 getCssVar('spacing', 'tight'), + // Radius + radius-circle: getCssVar(border, radius, medium), + // Font + font-item-font-size: getCssVar('tab-dropdown', 'font-size'), + // Other + right-offset: getCssVar('spacing', 'tight'), + box-shadow: getCssVar(shadow, elevated) +); + +@include b('tab-dropdown-list-content') { + @include set-component-css-var( + 'tab-dropdown-list-content', + $tab-dropdown-list-content + ); + + width: getCssVar(tab-dropdown-list-content, width); + padding-right: getCssVar(tab-dropdown-list-content, right-offset); + + @include e('wrapper') { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + padding: getCssVar(tab-dropdown-list-content, spacing-wrapper-padding); + background-color: getCssVar(tab-dropdown-list-content, color-wrapper-bg); + border-radius: getCssVar(tab-dropdown-list-content, radius-circle); + box-shadow: getCssVar(shadow, elevated); + } + + @include e('item') { + padding: getCssVar(tab-dropdown-list-content, spacing-item-padding); + font-size: getCssVar(tab-dropdown-list-content, font-item-font-size); + color: getCssVar(tab-dropdown-list-content, color-item-text); + white-space: nowrap; + + .#{bem(info-item)} { + width: 100%; + min-height: getCssVar(tab-dropdown-list-content, height-item); + } + .#{bem(info-item, label)} { + overflow: hidden; + text-overflow: ellipsis; + } + + @include when('active') { + color: getCssVar(tab-dropdown-list-content, color-item-text-active); + } + } + + @include e('modal') { + position: fixed; + top: 0; + left: 0; + z-index: 1; + width: 100vw; + height: 100vh; + } +} diff --git a/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.tsx b/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.tsx new file mode 100644 index 00000000000..f4d0efdb9d9 --- /dev/null +++ b/src/common/tab-layout/tab-dropdown/tab-dropdown-list-content.tsx @@ -0,0 +1,54 @@ +import { defineComponent, PropType } from 'vue'; +import { IModal } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { layoutProps } from '../tab-layout-util'; +import './tab-dropdown-list-content.scss'; + +export const TabDropdownListContent = defineComponent({ + name: 'IBizTabDropdownListContent', + props: { + modal: { + type: Object as PropType, + }, + ...layoutProps, + }, + setup(props) { + const ns = useNamespace('tab-dropdown-list-content'); + const handleItemClick = (activeName: string) => { + props.modal?.dismiss({ ok: true, data: [{ activeName }] }); + }; + const handleClose = () => { + props.modal?.dismiss(); + }; + + const handleClick = (e: MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + }; + + return { ns, handleClick, handleClose, handleItemClick }; + }, + render() { + return ( +
+
+
+ {this.tabPages.map(tab => { + if (tab.isHidden) return; + return ( +
this.handleItemClick(tab.id)} + > + +
+ ); + })} +
+
+ ); + }, +}); diff --git a/src/common/tab-layout/tab-dropdown/tab-dropdown.scss b/src/common/tab-layout/tab-dropdown/tab-dropdown.scss new file mode 100644 index 00000000000..83d111bed85 --- /dev/null +++ b/src/common/tab-layout/tab-dropdown/tab-dropdown.scss @@ -0,0 +1,49 @@ +$tab-dropdown: ( + // Color + color-text: getCssVar('tab-layout', 'color-tab-text'), + color-text-active: getCssVar('tab-layout', 'color-tab-text-active'), + // Width/Height + width-min-width: rem(40px), + width-max-width: rem(80px), + // Spacing + spacing-padding: getCssVar('spacing', 'tight'), + // Font + font-size: getCssVar('tab-layout', 'font-tab-font-size') +); + +@include b('tab-dropdown') { + @include set-component-css-var('tab-dropdown', $tab-dropdown); + + // 下拉列表气泡弹框样式 + @include e('list-modal') { + @include set-component-css-var('tab-dropdown', $tab-dropdown); + + background-color: unset; + border: unset; + box-shadow: unset; + } + + &.#{bem(float-button)} { + #{getCssVarName('float-button', 'width')}: auto; + #{getCssVarName('float-button', 'font-text-font-size')}: getCssVar( + tab-dropdown, + font-size + ); + #{getCssVarName('float-button', 'spacing-right-padding')}: getCssVar( + tab-dropdown, + spacing-padding + ); + min-width: getCssVar(tab-dropdown, width-min-width); + max-width: getCssVar(tab-dropdown, width-max-width); + + .#{bem(info-item)} { + width: 100%; + } + + .#{bem(info-item, label)} { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } +} diff --git a/src/common/tab-layout/tab-dropdown/tab-dropdown.tsx b/src/common/tab-layout/tab-dropdown/tab-dropdown.tsx new file mode 100644 index 00000000000..fcdb9017b0c --- /dev/null +++ b/src/common/tab-layout/tab-dropdown/tab-dropdown.tsx @@ -0,0 +1,81 @@ +import { computed, defineComponent, h, ref } from 'vue'; +import { IModal, IOverlayPopoverContainer } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { layoutEmits, layoutProps, useLayoutRender } from '../tab-layout-util'; +import { TabDropdownListContent } from './tab-dropdown-list-content'; +import './tab-dropdown.scss'; + +export const TabDropdown = defineComponent({ + name: 'IBizTabDropdown', + props: layoutProps, + emits: layoutEmits, + setup(props, { emit }) { + const ns = useNamespace('tab-dropdown'); + + const tabDropdownRef = ref(); + + const activeTab = computed(() => { + return props.tabPages.find(tab => tab.id === props.activeName); + }); + + const { onTabChange } = useLayoutRender(emit); + + let floatOverlay: IOverlayPopoverContainer | void; + const onFloatBtnClick = async ( + _event: MouseEvent, + _currentTarget: HTMLElement, + ) => { + if (floatOverlay) { + floatOverlay.dismiss(); + floatOverlay = undefined; + return; + } + + floatOverlay = ibiz.overlay.createPopover( + (modal: IModal) => { + return h(TabDropdownListContent, { + modal, + tabPages: props.tabPages, + activeName: props.activeName, + layoutMode: props.layoutMode, + }); + }, + {}, + { + width: 'auto', + height: 'auto', + noArrow: true, + autoClose: true, + placement: 'bottom-end', + modalClass: `${ns.e('list-modal')}`, + appendTo: tabDropdownRef.value?.$el, + } as IData, + ); + floatOverlay.present(_currentTarget); + + const res: IData = await floatOverlay.onWillDismiss(); + const activeKey = res?.data[0]?.activeName; + if (activeKey) { + onTabChange(activeKey); + } + floatOverlay = undefined; + }; + + return { ns, tabDropdownRef, activeTab, onFloatBtnClick }; + }, + render() { + return ( + + + + ); + }, +}); diff --git a/src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss new file mode 100644 index 00000000000..64cad06820b --- /dev/null +++ b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss @@ -0,0 +1,41 @@ +$tab-flow-layout: ( + // Color + color-group-header-text: getCssVar('tab-layout', '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('tab-layout', '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/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx new file mode 100644 index 00000000000..cef5bfe78b9 --- /dev/null +++ b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx @@ -0,0 +1,50 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { layoutEmits, layoutProps } from '../tab-layout-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'); + + return { ns }; + }, + render() { + const indexList = this.tabPages.map(_tab => _tab.text); + return ( + + {this.tabPages.map(tab => { + return ( +
+ + {this.layoutMode === 'flow_noheader' ? ( +
+ ) : ( +
+ +
+ )} +
+
+ {this.$slots.groupContent?.(tab)} +
+
+ ); + })} +
+ ); + }, +}); diff --git a/src/common/tab-layout/tab-layout-util.tsx b/src/common/tab-layout/tab-layout-util.tsx new file mode 100644 index 00000000000..0281fccd6ac --- /dev/null +++ b/src/common/tab-layout/tab-layout-util.tsx @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { PropType } from 'vue'; +import { ISysImage } from '@ibiz/model-core'; + +/** + * 分页项数据接口 + * @interface ITabItemData + */ +interface ITabItemData { + /** 唯一标识 */ + id: string; + /** 显示文本 */ + text?: string; + /** 图标资源信息 */ + icon?: ISysImage; + /** 计数器数值 */ + counter: string | number | null; + /** 是否隐藏 */ + isHidden?: boolean; +} + +/** + * 分页布局模式类型定义 + * @export + */ +export type LayoutMode = + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'flow' + | 'flow_noheader' + | 'top_dropdownlist'; + +/** + * 分页布局组件通用props + * @export + * @return {*} {IData} + */ +export const layoutProps = { + // 分页绘制数据集合 + tabPages: { + type: Array as PropType, + default: () => [], + }, + // 分页标识激活名称 + activeName: { + type: String, + default: '', + }, + // 布局模式 + layoutMode: { + type: String as PropType, + default: 'top', + }, +}; + +/** + * 分页布局组件通用emits + * @export + * @return {*} + */ +export const layoutEmits = { + /** + * @description 分页标识变更事件 + */ + tabChange: (_tabTag: string) => true, +}; + +/** + * 分页布局组件绘制通用方法 + * @export + */ +export function useLayoutRender( + emit: (event: 'tabChange', _tabTag: string) => void, +): { + onTabChange: (_tabTag: string) => void; +} { + const onTabChange = (_tabTag: string) => { + emit('tabChange', _tabTag); + }; + return { onTabChange }; +} diff --git a/src/common/tab-layout/tab-layout.scss b/src/common/tab-layout/tab-layout.scss new file mode 100644 index 00000000000..88fbc37ba79 --- /dev/null +++ b/src/common/tab-layout/tab-layout.scss @@ -0,0 +1,13 @@ +$tab-layout: ( + // Color + color-tab-text: getCssVar('color', 'text', 0), + color-tab-text-active: getCssVar('color', 'primary'), + // Font + font-tab-font-size: getCssVar('font-size', 'regular') +); +@include b('tab-layout') { + @include set-component-css-var('tab-layout', $tab-layout); + + width: 100%; + height: 100%; +} diff --git a/src/common/tab-layout/tab-layout.tsx b/src/common/tab-layout/tab-layout.tsx new file mode 100644 index 00000000000..8100d3ffc72 --- /dev/null +++ b/src/common/tab-layout/tab-layout.tsx @@ -0,0 +1,53 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { + layoutEmits, + LayoutMode, + layoutProps, + useLayoutRender, +} from './tab-layout-util'; +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 { TabDropdown } from './tab-dropdown/tab-dropdown'; +import './tab-layout.scss'; + +export const IBizTabLayout = defineComponent({ + name: 'IBizTabLayout', + props: layoutProps, + emits: layoutEmits, + setup(_props, { emit }) { + const ns = useNamespace('tab-layout'); + const { onTabChange } = useLayoutRender(emit); + return { ns, onTabChange }; + }, + render() { + const attrs = { + tabPages: this.tabPages, + activeName: this.activeName, + layoutMode: this.layoutMode as LayoutMode, + onTabChange: this.onTabChange, + }; + + let content = ; + + if (this.layoutMode === 'flow' || this.layoutMode === 'flow_noheader') { + const slots = {}; + if (this.$slots.groupContent) + Object.assign(slots, { + groupContent: this.$slots.groupContent, + }); + content = {slots}; + } + + if (['left', 'right'].includes(this.layoutMode)) { + content = ; + } + + if (['top_dropdownlist'].includes(this.layoutMode)) { + content = ; + } + + return
{content}
; + }, +}); diff --git a/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss new file mode 100644 index 00000000000..bc7e726a11b --- /dev/null +++ b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss @@ -0,0 +1,69 @@ +$tab-sidebar-layout: ( + // Color + color-bg: getCssVar(color, bg, 0), + color-item-bg: getCssVar(color, bg, 0), + // 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('tab-layout', '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); + .#{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/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx new file mode 100644 index 00000000000..8b005297627 --- /dev/null +++ b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx @@ -0,0 +1,58 @@ +import { defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { layoutEmits, layoutProps, useLayoutRender } from '../tab-layout-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 { onTabChange } = useLayoutRender(emit); + const onSidebarChange = (index: number) => { + activeIndex.value = index; + const tabTag = props.tabPages[index]?.id; + onTabChange(tabTag); + }; + + return { ns, activeIndex, onTabChange, onSidebarChange }; + }, + render() { + return ( + + {this.tabPages.map(tab => { + const isLongLabel = tab.text && tab.text.length > 3; + return ( + + {{ + title: () => ( +
+ +
+ ), + }} +
+ ); + })} +
+ ); + }, +}); -- Gitee From 1179b39a67f3add7f365520ee757e5cd9b5b38ff Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:14:51 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat=EF=BC=9A=E8=B0=83=E6=95=B4=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=9D=BF=E4=B8=8E=E6=95=B0=E6=8D=AE=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=E5=88=86=E9=A1=B5=E7=BB=98=E5=88=B6=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E9=80=9A=E7=94=A8=E7=BB=84=E4=BB=B6=E7=BB=98?= =?UTF-8?q?=E5=88=B6=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/drtab/drtab.scss | 66 ++---- src/control/drtab/drtab.tsx | 222 ++---------------- .../tab-exp-panel/layout-render-util.tsx | 90 ------- .../tab-default-layout.scss | 39 --- .../tab-default-layout/tab-default-layout.tsx | 73 ------ src/control/tab-exp-panel/tab-exp-panel.scss | 16 ++ src/control/tab-exp-panel/tab-exp-panel.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 ----- 11 files changed, 96 insertions(+), 718 deletions(-) delete mode 100644 src/control/tab-exp-panel/layout-render-util.tsx delete mode 100644 src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss delete mode 100644 src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx delete mode 100644 src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss delete mode 100644 src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx delete mode 100644 src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss delete mode 100644 src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx diff --git a/src/control/drtab/drtab.scss b/src/control/drtab/drtab.scss index 7bb408de6be..a0ad0528d41 100644 --- a/src/control/drtab/drtab.scss +++ b/src/control/drtab/drtab.scss @@ -1,56 +1,26 @@ $control-drtab: ( - 'active-color': getCssVar('color', 'primary'), - 'side-padding': getCssVar(spacing, base), - bg: getCssVar(color, bg, 1), - font-size: getCssVar(font-size, header-4), - popover-item-gap: getCssVar(spacing, extra, tight), - popover-padding: getCssVar(spacing, extra, loose), + // Color + color-tab-text: getCssVar('color', 'text', 0), + color-tab-text-active: getCssVar('color', 'primary'), + // Font + font-tab-font-size: getCssVar('font-size', 'regular') ); @include b(control-drtab) { @include set-component-css-var('control-drtab', $control-drtab); - background-color: getCssVar(control-drtab, bg); - @include m(top_dropdownlist) { - @include flex(row, center, center); - } - - @include e(dropdown-list) { - border: none; - font-size: getCssVar(control-drtab, font-size); - @include m(caption) { - font-size: getCssVar(control-drtab, font-size); - } - } - - @include e(popover) { - @include set-component-css-var('control-drtab', $control-drtab); - display: flex; - flex-direction: column; - padding-right: getCssVar(control-drtab, popover-padding); - @include m(item) { - border: none; - .van-button__text { - @include flex(row, center, center); - gap: getCssVar(control-drtab, popover-item-gap); - } - .van-button__content { - justify-content: left; - } - .van-button__icon { - color: getCssVar(control-drtab, active-color); - position: absolute; - right: calc(-0.5 * getCssVar(control-drtab, popover-padding)); - } - } - } - - @include e(sidebar) { - @include m(left) { - margin-left: getCssVar(control-drtab, side-padding); - } - @include m(right) { - margin-right: getCssVar(control-drtab, side-padding); - } + &.#{bem(tab-layout)} { + #{getCssVarName('tab-layout', 'color-tab-text')}: getCssVar( + control-drtab, + color-tab-text + ); + #{getCssVarName('tab-layout', 'color-tab-text-active')}: getCssVar( + control-drtab, + color-tab-text-active + ); + #{getCssVarName('tab-layout', 'font-tab-font-size')}: getCssVar( + control-drtab, + font-tab-font-size + ); } } diff --git a/src/control/drtab/drtab.tsx b/src/control/drtab/drtab.tsx index 9b7e7c56248..e83a7ced7a6 100644 --- a/src/control/drtab/drtab.tsx +++ b/src/control/drtab/drtab.tsx @@ -1,11 +1,4 @@ -import { - computed, - defineComponent, - onUnmounted, - PropType, - ref, - Ref, -} from 'vue'; +import { computed, defineComponent, PropType, ref, Ref } from 'vue'; import { useRouter } from 'vue-router'; import { IAppDETabExplorerView, IDEDRTab } from '@ibiz/model-core'; import { useControlController, useNamespace } from '@ibiz-template/vue3-util'; @@ -51,16 +44,10 @@ export const DRTabControl = defineComponent({ const showPopover: Ref = ref(false); - const tabPosition = + // 布局模式 + const layoutMode = (c.view.model as IAppDETabExplorerView).tabLayout?.toLowerCase() || 'top'; - const activeTab = computed(() => { - return c.state.drTabPages.find(tab => tab.tag === c.state.activeName); - }); - - // 激活下标,侧边栏使用 - const activeIndex = ref(0); - const fn = (counter: IData) => { counterData.value = counter; }; @@ -71,18 +58,6 @@ export const DRTabControl = defineComponent({ } }); - const emitChange = () => { - const { activeName } = c.state; - const drTabItem = c.state.drTabPages.find( - item => item.tag === activeName, - ); - if (drTabItem) { - c.evt.emit('onTabChange', { - data: drTabItem, - }); - } - }; - /** * 分页改变 * @param name @@ -91,189 +66,42 @@ export const DRTabControl = defineComponent({ c.state.activeName = name; showPopover.value = false; c.handleTabChange(); - emitChange(); - }; - - /** - * 打开/关闭 Popover - * @param e - */ - const onChangePopover = (e: MouseEvent) => { - e.stopPropagation(); - showPopover.value = !showPopover.value; }; - onUnmounted(() => { - c.counter?.offChange(fn); + // 分页绘制数据集合 + const tabPages = computed(() => { + return c.state.drTabPages.map(_tab => ({ + id: _tab.tag, + text: _tab.caption, + icon: _tab.sysImage, + counter: _tab.counterId ? counterData.value[_tab.counterId] : null, + isHidden: _tab.hidden, + })); }); - const onSidebarChange = (index: number) => { - activeIndex.value = index; - const value = c.state.drTabPages[index].tag; - c.state.activeName = value; - c.handleTabChange(); - emitChange(); - }; - - const renderDropdownList = () => { - return ( - - {{ - default: () => { - return ( -
- {c.state.drTabPages.map(tab => { - if (!tab.hidden) { - return ( - onTabChange(tab.tag)} - icon={tab.tag === c.state.activeName ? 'success' : ''} - icon-position='right' - > - {tab.sysImage && } -
- {tab.caption} - {tab.counterId && ( - - )} -
-
- ); - } - return null; - })} -
- ); - }, - reference: () => { - return ( - - {activeTab.value?.sysImage && ( - - )} - - {activeTab.value?.caption} - {activeTab.value?.counterId && ( - - )} - - - ); - }, - }} -
- ); - }; - - const renderDefault = () => { - const { drTabPages } = c.state; - if (['left', 'right'].includes(tabPosition)) { - return ( - - {drTabPages.map(tab => { - if (!tab.hidden) { - return ( - - ); - } - return null; - })} - - ); - } - return ( - - {c.state.drTabPages.map(tab => { - if (!tab.hidden) { - return ( - - {{ - title: () => ( -
- {tab.sysImage && ( - - )} - - {tab.caption} - -
- ), - }} -
- ); - } - return null; - })} -
- ); - }; - return { c, ns, - tabPosition, - renderDefault, - renderDropdownList, + tabPages, + layoutMode, + onTabChange, }; }, render() { - const { isCreated, isCalculatedPermission } = this.c.state; + const { isCreated, activeName, isCalculatedPermission } = this.c.state; return ( - {isCreated && - isCalculatedPermission && - this.tabPosition === 'top_dropdownlist' - ? this.renderDropdownList() - : this.renderDefault()} + {isCreated && isCalculatedPermission && ( + + )} ); }, diff --git a/src/control/tab-exp-panel/layout-render-util.tsx b/src/control/tab-exp-panel/layout-render-util.tsx deleted file mode 100644 index f1ac1e1ffbb..00000000000 --- a/src/control/tab-exp-panel/layout-render-util.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* 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 deleted file mode 100644 index b7c94788947..00000000000 --- a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss +++ /dev/null @@ -1,39 +0,0 @@ -$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 deleted file mode 100644 index fb05f887932..00000000000 --- a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx +++ /dev/null @@ -1,73 +0,0 @@ -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-exp-panel.scss b/src/control/tab-exp-panel/tab-exp-panel.scss index adc33184a4b..4d875328ab4 100644 --- a/src/control/tab-exp-panel/tab-exp-panel.scss +++ b/src/control/tab-exp-panel/tab-exp-panel.scss @@ -1,6 +1,7 @@ $control-tabexppanel: ( // Color color-tab-text: getCssVar('color', 'text', 0), + color-tab-text-active: getCssVar('color', 'primary'), // Font font-tab-font-size: getCssVar('font-size', 'regular') ); @@ -9,4 +10,19 @@ $control-tabexppanel: ( width: 100%; height: 100%; + + &.#{bem(tab-layout)} { + #{getCssVarName('tab-layout', 'color-tab-text')}: getCssVar( + control-tabexppanel, + color-tab-text + ); + #{getCssVarName('tab-layout', 'color-tab-text-active')}: getCssVar( + control-tabexppanel, + color-tab-text-active + ); + #{getCssVarName('tab-layout', 'font-tab-font-size')}: getCssVar( + control-tabexppanel, + font-tab-font-size + ); + } } diff --git a/src/control/tab-exp-panel/tab-exp-panel.tsx b/src/control/tab-exp-panel/tab-exp-panel.tsx index df7f04d0021..d26eced1615 100644 --- a/src/control/tab-exp-panel/tab-exp-panel.tsx +++ b/src/control/tab-exp-panel/tab-exp-panel.tsx @@ -1,20 +1,15 @@ import { useControlController, useNamespace } from '@ibiz-template/vue3-util'; -import { defineComponent, h, PropType, resolveComponent } from 'vue'; +import { computed, 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', @@ -57,51 +52,57 @@ export const TabExpPanelControl = defineComponent({ c.handleTabChange(); }; + // 分页绘制数据集合 + const tabPages = computed(() => { + return c.state.tabPages.map(_tab => ({ + id: _tab.tabTag, + text: _tab.caption, + icon: _tab.sysImage, + counter: _tab.counterId ? c.state.counterData[_tab.counterId] : null, + })); + }); + return { c, ns, + tabPages, layoutMode, onTabChange, }; }, render() { - const { isCreated, tabPages, counterData, activeName } = this.c.state; + const { isCreated, activeName } = this.c.state; if (!isCreated) { return; } - const attrs = { - tabPages, - counterData, - activeName, - layoutMode: this.layoutMode as LayoutMode, - onTabChange: this.onTabChange, - }; - let content = ; + let solts = {}; 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, - }); - }, - }} - - ); + solts = { + groupContent: (tab: IData) => { + const target = this.c.model.controls?.find( + ctrl => ctrl.id === tab.id, + ) as IDETabViewPanel; + return h(resolveComponent('IBizViewShell'), { + context: this.context, + params: this.params, + viewId: target?.embeddedAppDEViewId, + }); + }, + }; } - if (['left', 'right'].includes(this.layoutMode)) { - content = ; - } - - return
{content}
; + return ( + + {solts} + + ); }, }); 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 deleted file mode 100644 index 0807cb8554a..00000000000 --- a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss +++ /dev/null @@ -1,46 +0,0 @@ -$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 deleted file mode 100644 index aedfa931748..00000000000 --- a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -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 deleted file mode 100644 index 5c03aae4af7..00000000000 --- a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss +++ /dev/null @@ -1,72 +0,0 @@ -$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 deleted file mode 100644 index 75b239dec62..00000000000 --- a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx +++ /dev/null @@ -1,62 +0,0 @@ -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 efec5a6a7f72749e8d2a18574ea11a9d0b84a40e Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:16:24 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=85=B3=E7=B3=BB=E5=88=86=E9=A1=B5=E9=83=A8=E4=BB=B6?= =?UTF-8?q?=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/control/drtab/drtab.controller.ts | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/control/drtab/drtab.controller.ts b/src/control/drtab/drtab.controller.ts index 784d37beb40..8e9fd4a2554 100644 --- a/src/control/drtab/drtab.controller.ts +++ b/src/control/drtab/drtab.controller.ts @@ -18,6 +18,7 @@ import { import { getNestedRoutePath } from '@ibiz-template/vue3-util'; import { IDEDRCtrlItem, IDEDRTab } from '@ibiz/model-core'; import { Router } from 'vue-router'; +import { createUUID } from 'qx-util'; /** * 数据分页控制器 @@ -186,6 +187,33 @@ export class DRTabController } } + /** + * @description 获取当前激活项 + * @return {*} {IDEDRCtrlItem | undefined} + * @memberof DRTabController + */ + getActiveItem(): IDEDRCtrlItem | undefined { + return this.model.dedrtabPages?.find( + item => item.id === this.state.activeName, + ); + } + + /** + * @description 部件刷新 + * @memberof DRTabController + */ + refresh(): void { + const drBarItem = this.getActiveItem(); + + const isRoutePushed = true; + if (drBarItem) { + this.setVisible('navPos'); + this.openNavPosView(drBarItem, isRoutePushed, { + key: `${drBarItem.id}-${createUUID()}`, + }); + } + } + /** * @description 处理第一次的默认选中 * @memberof DRTabController @@ -437,6 +465,7 @@ export class DRTabController async openNavPosView( drTabPages: IDEDRCtrlItem, isRoutePushed = false, + opts = {}, ): Promise { const { context, params } = this.prepareParams(drTabPages); this.navPos?.openView({ @@ -448,6 +477,7 @@ export class DRTabController modalOptions: { replace: true, }, + ...opts, }); } -- Gitee From ec33c60f8e3bde3e4c08aa5f1fb7dbedc7b4f966 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:25:09 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=85=B3=E7=B3=BB=E5=88=86=E9=A1=B5=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E9=A1=B9=E9=80=BB=E8=BE=91=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=8F=8A=E8=AE=A1=E6=95=B0=E5=99=A8=E7=A6=81?= =?UTF-8?q?=E7=94=A8=E6=9C=AA=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/drtab/drtab.controller.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/control/drtab/drtab.controller.ts b/src/control/drtab/drtab.controller.ts index 8e9fd4a2554..736400e93c3 100644 --- a/src/control/drtab/drtab.controller.ts +++ b/src/control/drtab/drtab.controller.ts @@ -137,6 +137,7 @@ export class DRTabController * @memberof DRTabController */ protected async initCounter(): Promise { + if (this.state.isCounterDisabled) return; // todo 接口更新后换 const { appCounterRefs } = this.model as IData; const appCounterRef = appCounterRefs?.[0]; @@ -169,6 +170,11 @@ export class DRTabController } await this.calcDrTabPagesState(); this.handleFormChange(); + this.doDefaultSelect(); + if (this.state.hideEditItem) { + // 隐藏编辑项时清空视图信息栏文本 + this.view.evt.emit('onViewInfoChange', {}); + } }); this.form.evt.on('onLoadDraftSuccess', () => { this.handleFormChange(); -- Gitee From accfcd865cd4667f179adf62ec54996ffc867c85 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:28:18 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat=EF=BC=9A=E8=BF=81=E7=A7=BB=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=85=B3=E7=B3=BB=E5=88=86=E9=A1=B5=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=EF=BC=88=E5=88=86=E9=A1=B5=E5=88=87=E6=8D=A2?= =?UTF-8?q?=EF=BC=89=E8=87=B3=E6=8E=A7=E5=88=B6=E5=99=A8=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/drtab/drtab.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/control/drtab/drtab.controller.ts b/src/control/drtab/drtab.controller.ts index 736400e93c3..bb350fb3d67 100644 --- a/src/control/drtab/drtab.controller.ts +++ b/src/control/drtab/drtab.controller.ts @@ -392,6 +392,7 @@ export class DRTabController if (drBarItem) { this.setVisible('navPos'); this.openNavPosView(drBarItem, isRoutePushed); + this.evt.emit('onTabChange', { tab: drBarItem }); } else { this.setVisible('form'); if (this.routeDepth && this.state.drTabPages[0]) { -- Gitee From da35b7f680a6f07f12be21c41f03de96f6d21416 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:29:40 +0800 Subject: [PATCH 6/9] =?UTF-8?q?feat=EF=BC=9A=E4=BC=98=E5=8C=96=E6=B5=AE?= =?UTF-8?q?=E5=8A=A8=E6=8C=89=E9=92=AE=E5=8F=8A=E4=BF=A1=E6=81=AF=E9=A1=B9?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E7=BB=84=E4=BB=B6=E7=9A=84=E7=BB=98=E5=88=B6?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=8F=8A=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/float-button/float-button.scss | 3 +++ src/common/float-button/float-button.tsx | 8 ++++++-- src/common/info-item/info-item.tsx | 2 -- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/float-button/float-button.scss b/src/common/float-button/float-button.scss index 6ebbd5d2467..c2bc4c6f780 100644 --- a/src/common/float-button/float-button.scss +++ b/src/common/float-button/float-button.scss @@ -50,6 +50,8 @@ $float-button: ( 'transform': unset, // 按钮变换样式 'border': none, + // 按钮层级 + 'z-index': 1, ); @include b(float-button) { @@ -73,6 +75,7 @@ $float-button: ( transform: getCssVar(float-button, transform); color: getCssVar(float-button, color-text); font-size: getCssVar(float-button, font-text-font-size); + z-index: getCssVar(float-button, z-index); @include e(icon) { display: flex; diff --git a/src/common/float-button/float-button.tsx b/src/common/float-button/float-button.tsx index 250cdebb08e..7750e379164 100644 --- a/src/common/float-button/float-button.tsx +++ b/src/common/float-button/float-button.tsx @@ -1,5 +1,5 @@ import { defineComponent, PropType, ref } from 'vue'; -import { useNamespace } from '@ibiz-template/vue3-util'; +import { useNamespace, useUIStore } from '@ibiz-template/vue3-util'; import './float-button.scss'; export const FloatButton = defineComponent({ @@ -22,13 +22,16 @@ export const FloatButton = defineComponent({ setup(props, { emit }) { const ns = useNamespace('float-button'); + const { zIndex } = useUIStore(); + const popoverZIndex = zIndex.increment(); + const buttonRef = ref(); const handleClick = (_event: MouseEvent) => { emit('click', _event, buttonRef.value); }; - return { ns, buttonRef, handleClick }; + return { ns, popoverZIndex, buttonRef, handleClick }; }, render() { const align = this.align.toLocaleLowerCase(); @@ -43,6 +46,7 @@ export const FloatButton = defineComponent({ this.ns.is(align, !!align), align ? this.ns.bm('customized', align) : '', ]} + style={{ [this.ns.cssVarBlockName('z-index')]: this.popoverZIndex }} onClick={this.handleClick} > {defaultSlot || [ diff --git a/src/common/info-item/info-item.tsx b/src/common/info-item/info-item.tsx index ec686435a29..afa6702c952 100644 --- a/src/common/info-item/info-item.tsx +++ b/src/common/info-item/info-item.tsx @@ -15,14 +15,12 @@ export const IBizInfoItem = defineComponent({ */ label: { type: String, - default: '', }, /** * @description 计数器数值 */ badge: { type: Number, - default: null, }, /** * @description 图标资源信息 -- Gitee From 7b93343a83f8edb232281ef5f797eb4504989647 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:32:36 +0800 Subject: [PATCH 7/9] =?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=E5=BC=95=E6=93=8E=E7=9A=84=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E5=88=B7=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E5=85=B6=E4=B8=8Epc=E7=AB=AF=E8=A7=86=E5=9B=BE=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view-engine/mob-tab-exp-view.engine.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view-engine/mob-tab-exp-view.engine.ts b/src/view-engine/mob-tab-exp-view.engine.ts index 786125e9274..e0cc9c5ba64 100644 --- a/src/view-engine/mob-tab-exp-view.engine.ts +++ b/src/view-engine/mob-tab-exp-view.engine.ts @@ -225,6 +225,7 @@ export class MobTabExpViewEngine extends ViewEngineBase { * @memberof MobTabExpViewEngine */ protected async refresh(): Promise { + await this.loadEntityData(); this.tabExpPanel.refresh(); } -- Gitee From f630ca411cfffaa20a7c2ddc1c5b0ae258b61859 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:32:51 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/dashboard/portlet/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/control/dashboard/portlet/index.ts b/src/control/dashboard/portlet/index.ts index bd429a3c395..005ac93151a 100644 --- a/src/control/dashboard/portlet/index.ts +++ b/src/control/dashboard/portlet/index.ts @@ -1,4 +1,3 @@ -export * from './portlet-part'; export * from './container-portlet'; export * from './portlet-layout/portlet-layout'; export * from './menu-portlet'; -- Gitee From 28501bde9ba0f58c9d53a67e587f3264d9df18a3 Mon Sep 17 00:00:00 2001 From: lijianxiong <1518062161@qq.com> Date: Thu, 13 Nov 2025 09:33:08 +0800 Subject: [PATCH 9/9] =?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 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7d3200838..dbebf30bae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,8 @@ - 新增日历时间轴加载更多 - 新增工具栏分组列表、浮动工具栏组件,用于绘制分组弹框内容及自定义工具栏内容 - 修改移动端编辑视图(分页关系)刷新视图逻辑 +- 新增分页布局通用组件,支持模型配置分页栏位置,适配配置项(上方、下方、左侧、右侧、流布局、流布局(无标题)、上方(下拉列表)) +- 新增数据关系分页部件刷新能力 ### Change @@ -116,6 +118,10 @@ - 重构工具栏绘制逻辑,抽离冗余逻辑到hook中统一管理 - 优化工具栏部件组件样式,根据主题调整原则优化 - 增强浮动按钮组件抛值逻辑及显示样式 +- 调整分页面板与数据关系分页绘制逻辑,统一使用分页布局通用组件绘制分页 +- 迁移数据关系分页通知事件(分页切换)至控制器中 +- 优化浮动按钮及信息项通用组件的绘制逻辑及样式 +- 优化分页导航引擎的视图刷新逻辑,使其与pc端视图刷新保持一致 ### Fixed @@ -141,6 +147,7 @@ - 修复日历标记显示异常 - 修复菜单图标样式呈现不正确异常 - 修复开启下拉刷新后视图返回按钮失效异常 +- 修复数据关系分页隐藏编辑项逻辑计算异常及计数器禁用未生效 ## [0.7.41-alpha.19] - 2025-10-16 -- Gitee