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