From 8b31600e3f4b18f2d3773810c5d1f0d7d52bf4c3 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 27 Sep 2024 16:02:46 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=20=E5=B8=83=E5=B1=80=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E6=96=B0=E5=A2=9E=E5=B7=A6=E4=BE=A7=E8=BE=B9=E6=A0=8F?= =?UTF-8?q?=E8=8F=9C=E5=8D=95spanmode=E6=A8=A1=E5=BC=8F=EF=BC=8C=E9=9D=9E?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=89=93=E5=BC=80=E6=96=B9=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E5=99=A8=E5=8A=9F=E8=83=BD=E5=92=8C=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E9=A1=B9=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design-left-menu.controller.ts | 65 +++++++++++++++++ .../design-left-menu/design-left-menu.scss | 19 +++++ .../design-left-menu/design-left-menu.tsx | 70 +++++++++++++++++-- 3 files changed, 147 insertions(+), 7 deletions(-) diff --git a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts index 2d44aad4..64a8a58f 100644 --- a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts +++ b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts @@ -1,8 +1,12 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-await-in-loop */ import { + AppCounter, + getAppMenuItemProvider, getControl, + IAppMenuItemProvider, IPanelItemController, + OpenAppViewCommand, PanelItemController, ViewController, } from '@ibiz-template/runtime'; @@ -85,6 +89,15 @@ export class DesignLeftMenuController extends PanelItemController = new Map(); + /** + * 菜单项适配器 + * + * @protected + * @type {{ [key: string]: IAppMenuItemProvider }} + * @memberof DesignLeftMenuController + */ + public itemProviders: { [key: string]: IAppMenuItemProvider } = {}; + /** * 初始化 * @@ -96,6 +109,7 @@ export class DesignLeftMenuController extends PanelItemController { await super.onInit(); await this.initState(); + await this.initAppMenuItemProviders(); } /** @@ -115,6 +129,39 @@ export class DesignLeftMenuController extends PanelItemController} + * @memberof DesignLeftMenuController + */ + protected async initAppMenuItemProviders(): Promise { + if (!this.leftSideMenu || !this.leftSideMenu.appMenuItems) return; + await Promise.all( + this.leftSideMenu.appMenuItems.map(async item => { + const provider = await getAppMenuItemProvider(item); + if (provider) { + this.itemProviders[item.id!] = provider; + } + }), + ); + } + + /** + * 获取计数器对象 + * + * @return {*} {(AppCounter | null)} + * @memberof DesignLeftMenuController + */ + getCounter(): AppCounter | null { + if (this.leftSideMenu.appCounterRefId) { + const { counters } = this.panel.view; + return counters[this.leftSideMenu.appCounterRefId]; + } + return null; + } + /** * 处理菜单项点击 * @@ -153,6 +200,24 @@ export class DesignLeftMenuController extends PanelItemController { const firstViewID = await this.getViewIDByMenuItem(appMenuItem); + if (firstViewID) { + const viewConfig = await ibiz.hub.config.view.get(firstViewID); + if ( + viewConfig.openMode && + ![ + 'INDEXVIEWTAB', + 'INDEXVIEWTAB_POPUP', + 'INDEXVIEWTAB_POPUPMODAL', + ].includes(viewConfig.openMode) + ) { + return ibiz.commands.execute( + OpenAppViewCommand.TAG, + firstViewID, + this.context, + this.params, + ); + } + } this.state.activeMenuItem = appMenuItem; this.state.activeViewModelId = firstViewID; this.context.srfappid = appMenuItem.appId; diff --git a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.scss b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.scss index 4c506fb4..1f946023 100644 --- a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.scss +++ b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.scss @@ -3,11 +3,23 @@ width: 100%; height: 100%; + @include e(counter) { + right: 8px; + bottom: 8px; + width: 16px !important; + height: 16px !important; + font-size: 9px !important; + line-height: 16px !important; + position: absolute; + } + @include e(side-container) { flex: none; width: 48px; height: 100%; overflow: auto; + display: flex; + flex-direction: column; color: getCssVar(color, text, 3); background: getCssVar(color, bg, 2); @@ -15,8 +27,10 @@ display: flex; align-items: center; justify-content: center; + position: relative; width: 48px; height: 48px; + flex-shrink: 0; font-size: getCssVar('font-size', 'header-3'); cursor: pointer; @@ -24,6 +38,11 @@ color: getCssVar(color, primary, text); border-left: 2px solid getCssVar(color, primary, text); } + + @include when(span-mode) { + flex-grow: 1; + cursor: default; + } &:hover { color: getCssVar(color, primary, hover, text); diff --git a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx index 3ee66dd3..20b2cd39 100644 --- a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx +++ b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx @@ -1,6 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { PropType, defineComponent, h, ref, resolveComponent } from 'vue'; +import { + h, + ref, + PropType, + onMounted, + onUnmounted, + defineComponent, + resolveComponent, +} from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; +import { AppCounter } from '@ibiz-template/runtime'; import { IAppMenuItem, IPanelRawItem } from '@ibiz/model-core'; import { DesignLeftMenuController } from './design-left-menu.controller'; import './design-left-menu.scss'; @@ -27,9 +37,32 @@ export default defineComponent({ const draging = ref(false); + // 计数器数据 + let counter: AppCounter | null = null; + const counterData = ref({}); + + const fn = (data: IData) => { + counterData.value = data; + }; + + onMounted(() => { + // 计数器相关 + counter = c.getCounter(); + if (counter) { + counter.onChange(fn); + } + }); + + onUnmounted(() => { + counter?.offChange(fn); + counter?.destroy(); + }); + // 处理点击事件 const handleClick = (menuItem: IAppMenuItem, event: MouseEvent) => { - c.handleMenuItemClick(menuItem, event); + if (menuItem.itemType === 'MENUITEM') { + c.handleMenuItemClick(menuItem, event); + } }; // 处理菜单拖拽 @@ -79,12 +112,30 @@ export default defineComponent({ ); }; + const renderCounter = (menuItem: IAppMenuItem) => { + const counterId = menuItem.counterId; + if (counterId && counterData.value[counterId] != null) { + return ( + + ); + } + }; + // 绘制左侧菜单栏 const renderMenuItems = () => { - return c.state.allMenuItems.map(menuItem => { + const spanModeIndex = c.state.allMenuItems.findIndex( + menuItem => menuItem.itemType === 'SEPERATOR' && menuItem.spanMode, + ); + return c.state.allMenuItems.map((menuItem, index) => { + const provider = c.itemProviders[menuItem.id!]; const itemClass = [ ns.em('side-container', 'item'), + ns.em('side-container', `${menuItem.itemType?.toLowerCase()}`), ns.is('active', c.state.activeMenuItem?.id === menuItem.id), + ns.is('span-mode', spanModeIndex === index), ]; return (
handleClick(menuItem, e)} > - + {provider && provider.renderText + ? provider.renderText(menuItem, c as any) + : [ + , + renderCounter(menuItem), + ]}
); }); -- Gitee From 67d9a1f218ae41897777918d54b361254d1633d4 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Fri, 27 Sep 2024 18:38:15 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E8=AE=BE=E8=AE=A1=E5=B7=A6=E4=BE=A7=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=8B=96=E6=8B=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design-left-menu.controller.ts | 151 +++++++++++++++--- .../design-left-menu/design-left-menu.tsx | 146 +++++++++++++---- 2 files changed, 251 insertions(+), 46 deletions(-) diff --git a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts index 64a8a58f..e3cace99 100644 --- a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts +++ b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.controller.ts @@ -2,6 +2,7 @@ /* eslint-disable no-await-in-loop */ import { AppCounter, + ConfigService, getAppMenuItemProvider, getControl, IAppMenuItemProvider, @@ -99,45 +100,49 @@ export class DesignLeftMenuController extends PanelItemController} + * @return {*} {DesignLeftMenuState} + * @memberof DesignLeftMenuController */ - protected async onInit(): Promise { - await super.onInit(); - await this.initState(); - await this.initAppMenuItemProviders(); + protected createState(): DesignLeftMenuState { + return new DesignLeftMenuState(this.parent?.state); } /** - * 初始化状态 + * 初始化 * * @author tony001 - * @date 2024-08-13 17:08:34 + * @date 2024-08-13 16:08:14 * @protected * @return {*} {Promise} */ - protected async initState(): Promise { - this.context = clone(this.panel.context); - this.params = clone(this.panel.params); - if (!this.leftSideMenu || !this.leftSideMenu.appMenuItems) return; - this.state.allMenuItems = this.leftSideMenu.appMenuItems; - const firstMenuItem = this.leftSideMenu.appMenuItems[0]; - this.setActiveMenuItem(firstMenuItem); + protected async onInit(): Promise { + await super.onInit(); + await this.initAppMenuItem(); } /** - * 初始化菜单项的适配器 + * 初始化应用菜单 * * @protected * @return {*} {Promise} * @memberof DesignLeftMenuController */ - protected async initAppMenuItemProviders(): Promise { + protected async initAppMenuItem(): Promise { + this.context = clone(this.panel.context); + this.params = clone(this.panel.params); if (!this.leftSideMenu || !this.leftSideMenu.appMenuItems) return; + // 初始化菜单项适配器 await Promise.all( this.leftSideMenu.appMenuItems.map(async item => { const provider = await getAppMenuItemProvider(item); @@ -146,6 +151,114 @@ export class DesignLeftMenuController extends PanelItemController order.key)); + + // 根据 orders 的顺序填充 sortedArray + orders.forEach(order => { + const menuItem = menuItems.find(item => item.id === order.key); + if (menuItem) { + sortedArray[order.index] = menuItem; + } + }); + + // 找到所有不在 orders 中的菜单项 + const remainingMenuItems = menuItems.filter( + item => !existingKeys.has(item.id!), + ); + + // 将剩余元素添加到 sortedArray 的末尾 + sortedArray.push(...remainingMenuItems); + + // 过滤掉未定义的元素(可能由于 orders 中的 index 超出范围) + return sortedArray.filter(Boolean); + } + + /** + * 加载自定义模型 + * + * @return {*} {Promise< + * { + * key: string; + * index: number; + * }[] + * >} + * @memberof DesignLeftMenuController + */ + async loadCustomModelData(): Promise< + { + key: string; + index: number; + }[] + > { + const res = await this.config!.load(); + if (res && res.model) { + return JSON.parse(res.model); + } + return []; + } + + /** + * 保存自定义模型 + * + * @param {{ + * key: string; + * index: number; + * }[]} model + * @return {*} {Promise< + * { + * key: string; + * index: number; + * }[] + * >} + * @memberof DesignLeftMenuController + */ + async saveCustomModelData( + model: { + key: string; + index: number; + }[], + ): Promise< + { + key: string; + index: number; + }[] + > { + const data: IData = { + model: JSON.stringify(model), + }; + await this.config!.save(data); + return model; } /** diff --git a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx index 20b2cd39..8a7b8ce4 100644 --- a/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx +++ b/packages/layout-design/src/design-index-view/components/design-left-menu/design-left-menu.tsx @@ -12,11 +12,15 @@ import { import { useNamespace } from '@ibiz-template/vue3-util'; import { AppCounter } from '@ibiz-template/runtime'; import { IAppMenuItem, IPanelRawItem } from '@ibiz/model-core'; +import draggable from 'vuedraggable'; import { DesignLeftMenuController } from './design-left-menu.controller'; import './design-left-menu.scss'; export default defineComponent({ name: 'IBizDesignLeftMenu', + components: { + draggable, + }, props: { modelData: { type: Object as PropType, @@ -112,6 +116,31 @@ export default defineComponent({ ); }; + /** + * 拖拽改变 + * + * @param {IData} evt + */ + const onDraggableChange = (evt: IData) => { + if (evt.moved) { + const movetItem = c.state.allMenuItems.splice(evt.moved.oldIndex, 1)[0]; + c.state.allMenuItems.splice(evt.moved.newIndex, 0, movetItem); + const configs = c.state.allMenuItems.map((menu, index) => { + return { + key: menu.id!, + index, + }; + }); + c.saveCustomModelData(configs); + } + }; + + /** + * 绘制定时器 + * + * @param {IAppMenuItem} menuItem + * @return {*} + */ const renderCounter = (menuItem: IAppMenuItem) => { const counterId = menuItem.counterId; if (counterId && counterData.value[counterId] != null) { @@ -124,37 +153,100 @@ export default defineComponent({ } }; - // 绘制左侧菜单栏 + /** + * 绘制菜单项 + * + * @param {IAppMenuItem} menuItem + * @return {*} + */ + const renderMenuItem = (menuItem: IAppMenuItem) => { + const provider = c.itemProviders[menuItem.id!]; + return ( +
handleClick(menuItem, e)} + > + {provider && provider.renderText + ? provider.renderText(menuItem, c as any) + : [ + , + renderCounter(menuItem), + ]} +
+ ); + }; + + /** + * 绘制左侧菜单栏 + * + * @return {*} + */ const renderMenuItems = () => { const spanModeIndex = c.state.allMenuItems.findIndex( menuItem => menuItem.itemType === 'SEPERATOR' && menuItem.spanMode, ); - return c.state.allMenuItems.map((menuItem, index) => { - const provider = c.itemProviders[menuItem.id!]; - const itemClass = [ - ns.em('side-container', 'item'), - ns.em('side-container', `${menuItem.itemType?.toLowerCase()}`), - ns.is('active', c.state.activeMenuItem?.id === menuItem.id), - ns.is('span-mode', spanModeIndex === index), - ]; - return ( -
handleClick(menuItem, e)} + return ( +
+ onDraggableChange(evt)} > - {provider && provider.renderText - ? provider.renderText(menuItem, c as any) - : [ - , - renderCounter(menuItem), - ]} -
- ); - }); + {{ + item: ({ + element, + index, + }: { + element: IAppMenuItem; + index: number; + }) => { + if (!spanModeIndex || index < spanModeIndex) { + return renderMenuItem(element); + } + }, + }} + + {spanModeIndex && [ +
, + onDraggableChange(evt)} + > + {{ + item: ({ + element, + index, + }: { + element: IAppMenuItem; + index: number; + }) => { + if (index > spanModeIndex) { + return renderMenuItem(element); + } + }, + }} + , + ]} +
+ ); }; // 绘制视图区域 @@ -194,7 +286,7 @@ export default defineComponent({ return (
-
{this.renderMenuItems()}
+ {this.renderMenuItems()}
{this.renderContentView()}
{!this.c.state.collapse && (