diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7d32008385f0b8626fb68386ddcfbd7fcf61b6..dbebf30bae5187e0c1957842a97476e54e88175a 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 diff --git a/src/common/float-button/float-button.scss b/src/common/float-button/float-button.scss index 6ebbd5d2467b64a4729c09e98c6236aaed4b0e01..c2bc4c6f78020aa84cae194c5184d9811a1ba9d2 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 250cdebb08e92c82631a6e61044e8fb03116aa92..7750e379164f9fa253a19a37239c0334f9cb1c35 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/index.ts b/src/common/index.ts index 8ba5c534ae9773ab3c166588a8196a8d332d28ab..1ae808edad0298fab5ea44be26e9b683a6a10f66 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/info-item/info-item.tsx b/src/common/info-item/info-item.tsx index ec686435a2949eb4893abe5fea52c4cd07949187..afa6702c9520361d32ead5c696eb4dfecb1719cb 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 图标资源信息 diff --git a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss b/src/common/tab-layout/tab-default-layout/tab-default-layout.scss similarity index 78% rename from src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss rename to src/common/tab-layout/tab-default-layout/tab-default-layout.scss index b7c94788947ffb2cc85cc088dff2dd1b67d70654..a5ed3f642a8eb4f0ac6c0637a42c83875d9988f8 100644 --- a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.scss +++ b/src/common/tab-layout/tab-default-layout/tab-default-layout.scss @@ -1,18 +1,16 @@ $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') + 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') { - color: getCssVar(tab-default-layout, color-item-text); font-size: getCssVar(tab-default-layout, font-tab-font-size); } diff --git a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx b/src/common/tab-layout/tab-default-layout/tab-default-layout.tsx similarity index 70% rename from src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx rename to src/common/tab-layout/tab-default-layout/tab-default-layout.tsx index fb05f887932e4258b8ba590e0865c523ac2c80e6..32934890e7233172d4c54d166ed110c7e72af5b8 100644 --- a/src/control/tab-exp-panel/tab-default-layout/tab-default-layout.tsx +++ b/src/common/tab-layout/tab-default-layout/tab-default-layout.tsx @@ -1,36 +1,34 @@ import { defineComponent } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; -import { - layoutEmits, - layoutProps, - useLayoutRender, -} from '../layout-render-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 }) { + setup(_props, { emit }) { const ns = useNamespace('tab-default-layout'); - const { getCounterValue, onTabChange } = useLayoutRender(props, emit, ns); + const { onTabChange } = useLayoutRender(emit); - return { ns, getCounterValue, onTabChange }; + return { ns, onTabChange }; }, render() { let content = ( {this.tabPages.map(tab => { + if (tab.isHidden) return; + return ( - + {{ title: () => ( ), }} @@ -43,15 +41,17 @@ export const TabDefaultLayout = defineComponent({ content = ( {this.tabPages.map(tab => { + if (tab.isHidden) return; + return ( - + {{ default: () => ( ), 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 0000000000000000000000000000000000000000..465fb159856c1986570393b2bdf1d3730f2611a2 --- /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 0000000000000000000000000000000000000000..f4d0efdb9d9ad4aceac2f78627551d33c49b872d --- /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 0000000000000000000000000000000000000000..83d111bed85c2cae06fb974d158ffd0155cbdef6 --- /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 0000000000000000000000000000000000000000..fcdb9017b0c2d2c387ef3053c7fdc7e6195602ad --- /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/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss similarity index 74% rename from src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss rename to src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss index 0807cb8554afe213e6197be049e3d46bbdf3ba3a..64cad06820bda9d38c6c6ef818e2f4277a6219af 100644 --- a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.scss +++ b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.scss @@ -1,20 +1,15 @@ $tab-flow-layout: ( // Color - color-group-header-text: getCssVar('control-tabexppanel', 'color-tab-text'), + 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'), + height-group-header-content-min-height: getCssVar('height-control', 'default'), // Spacing - spacing-group-header-padding: getCssVar('spacing', 'tight') - getCssVar('spacing', 'base'), + 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' - ) + 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); diff --git a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx similarity index 74% rename from src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx rename to src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx index aedfa931748fd10744c95d134f82cae19926ddca..cef5bfe78b98ca2ae732477b03251bb31c429039 100644 --- a/src/control/tab-exp-panel/tab-flow-layout/tab-flow-layout.tsx +++ b/src/common/tab-layout/tab-flow-layout/tab-flow-layout.tsx @@ -1,10 +1,6 @@ import { defineComponent } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; -import { - layoutEmits, - layoutProps, - useLayoutRender, -} from '../layout-render-util'; +import { layoutEmits, layoutProps } from '../tab-layout-util'; import './tab-flow-layout.scss'; export const TabFlowLayout = defineComponent({ @@ -13,12 +9,11 @@ export const TabFlowLayout = defineComponent({ emits: layoutEmits, setup(props, { emit }) { const ns = useNamespace('tab-flow-layout'); - const { getCounterValue } = useLayoutRender(props, emit, ns); - return { ns, getCounterValue }; + return { ns }; }, render() { - const indexList = this.tabPages.map(x => x.caption); + const indexList = this.tabPages.map(_tab => _tab.text); return ( { return (
- + {this.layoutMode === 'flow_noheader' ? (
) : (
)} diff --git a/src/control/tab-exp-panel/layout-render-util.tsx b/src/common/tab-layout/tab-layout-util.tsx similarity index 47% rename from src/control/tab-exp-panel/layout-render-util.tsx rename to src/common/tab-layout/tab-layout-util.tsx index f1ac1e1ffbbfb72b60a0735f752897738265357d..0281fccd6ac8272edc50cb8a6480ccb71c8a99dc 100644 --- a/src/control/tab-exp-panel/layout-render-util.tsx +++ b/src/common/tab-layout/tab-layout-util.tsx @@ -1,44 +1,48 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { Namespace } from '@ibiz-template/core'; -import { IApiTabExpPanelPagesState } from '@ibiz-template/runtime'; 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'; - -interface ILayoutProps { - // 分页绘制数据集合 - tabPages: IApiTabExpPanelPagesState[]; - // 计数器数据 - counterData: IData; - // 分页标识激活名称 - activeName: string; - // 布局模式 - layoutMode: LayoutMode; -} + | 'flow_noheader' + | 'top_dropdownlist'; /** - * 布局组件通用props - * + * 分页布局组件通用props * @export * @return {*} {IData} */ export const layoutProps = { // 分页绘制数据集合 tabPages: { - type: Array as PropType, + type: Array as PropType, default: () => [], }, - // 计数器数据 - counterData: { - type: Object as PropType, - default: () => ({}), - }, // 分页标识激活名称 activeName: { type: String, @@ -52,10 +56,9 @@ export const layoutProps = { }; /** - * 布局组件通用emits - * + * 分页布局组件通用emits * @export - * @return {*} {IData} + * @return {*} */ export const layoutEmits = { /** @@ -65,26 +68,16 @@ export const layoutEmits = { }; /** - * 布局组件通用方法 - * + * 分页布局组件绘制通用方法 * @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 }; + 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 0000000000000000000000000000000000000000..88fbc37ba79fb936e8a731d3056397f62234209e --- /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 0000000000000000000000000000000000000000..8100d3ffc72dccfdea16e9e0187ed65e586371ce --- /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/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss similarity index 88% rename from src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss rename to src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss index 5c03aae4af7652b1ceb1972e27c55d1351ace624..bc7e726a11bb9744750dc198cc41d22aec0f75bf 100644 --- a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.scss +++ b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.scss @@ -1,9 +1,7 @@ $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%, @@ -14,7 +12,7 @@ $tab-sidebar-layout: ( spacing-sidebar-item-padding: getCssVar('spacing', 'tight') getCssVar('spacing', 'tight'), // Font - font-tab-font-size: getCssVar('control-tabexppanel', 'font-tab-font-size') + 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); @@ -30,7 +28,6 @@ $tab-sidebar-layout: ( 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%; diff --git a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx similarity index 68% rename from src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx rename to src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx index 75b239dec620adaab8f26efb14e6e09a8b6d3d03..8b005297627180d90c5586ba87f5fb4edf53bdc3 100644 --- a/src/control/tab-exp-panel/tab-sidebar-layout/tab-sidebar-layout.tsx +++ b/src/common/tab-layout/tab-sidebar-layout/tab-sidebar-layout.tsx @@ -1,10 +1,6 @@ import { defineComponent, ref } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; -import { - layoutEmits, - layoutProps, - useLayoutRender, -} from '../layout-render-util'; +import { layoutEmits, layoutProps, useLayoutRender } from '../tab-layout-util'; import './tab-sidebar-layout.scss'; export const TabSidebarLayout = defineComponent({ @@ -17,14 +13,14 @@ export const TabSidebarLayout = defineComponent({ // 激活下标 const activeIndex = ref(0); - const { getCounterValue, onTabChange } = useLayoutRender(props, emit, ns); + const { onTabChange } = useLayoutRender(emit); const onSidebarChange = (index: number) => { activeIndex.value = index; - const tabTag = props.tabPages[index].tabTag; + const tabTag = props.tabPages[index]?.id; onTabChange(tabTag); }; - return { ns, activeIndex, getCounterValue, onTabChange, onSidebarChange }; + return { ns, activeIndex, onTabChange, onSidebarChange }; }, render() { return ( @@ -34,7 +30,7 @@ export const TabSidebarLayout = defineComponent({ onChange={this.onSidebarChange} > {this.tabPages.map(tab => { - const isLongLabel = tab.caption.length > 3; + const isLongLabel = tab.text && tab.text.length > 3; return ( {{ @@ -42,13 +38,13 @@ export const TabSidebarLayout = defineComponent({
), diff --git a/src/control/dashboard/portlet/index.ts b/src/control/dashboard/portlet/index.ts index bd429a3c395a086b15b70e786cb7743ff00034f5..005ac93151a4a53113318dc451dbb3d564d55dc6 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'; diff --git a/src/control/drtab/drtab.controller.ts b/src/control/drtab/drtab.controller.ts index 784d37beb4029621e467e31dbdfe85356bb5c088..105414d07f11dcde2edb33cbb03bc20159aa2424 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'; /** * 数据分页控制器 @@ -136,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]; @@ -168,6 +170,7 @@ export class DRTabController } await this.calcDrTabPagesState(); this.handleFormChange(); + this.doDefaultSelect(); }); this.form.evt.on('onLoadDraftSuccess', () => { this.handleFormChange(); @@ -186,6 +189,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 @@ -358,6 +388,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]) { @@ -437,6 +468,7 @@ export class DRTabController async openNavPosView( drTabPages: IDEDRCtrlItem, isRoutePushed = false, + opts = {}, ): Promise { const { context, params } = this.prepareParams(drTabPages); this.navPos?.openView({ @@ -448,6 +480,7 @@ export class DRTabController modalOptions: { replace: true, }, + ...opts, }); } diff --git a/src/control/drtab/drtab.scss b/src/control/drtab/drtab.scss index 7bb408de6be264e1d950a79cdcf6bc798c1d72da..a0ad0528d41d49deb0702c65af4073eb5619a30b 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 9b7e7c56248e100dc43d5a1f0d9a41bffe155f40..e83a7ced7a6f64153e3b9a35976b3c43c54bfbe7 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/tab-exp-panel.scss b/src/control/tab-exp-panel/tab-exp-panel.scss index adc33184a4bfa0460b7b03292e8232f8a419697a..4d875328ab4ad39213e297c8c94e092b98140c27 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 df7f04d00219d5db418f3892b209e74eec821e22..d26eced1615cd38c59adc4fc7044e370a4c02b3a 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/view-engine/mob-tab-exp-view.engine.ts b/src/view-engine/mob-tab-exp-view.engine.ts index 786125e9274dc2736d2abdbac8cfd517dc9ff17f..e0cc9c5ba64f5ce482da28efceb97f4c7d27c29d 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(); }