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 147be3d8328fb3182654b497829a8b4a33095321..5936fceecb4bb79c0b89468726b844f38b18d3b9 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 { getControl, + IPanelItemController, PanelItemController, ViewController, } from '@ibiz-template/runtime'; @@ -43,6 +44,17 @@ export class DesignLeftMenuController extends PanelItemController { + let resetWidth = false; if (menuItem.id !== this.state.activeMenuItem?.id) { await this.setActiveMenuItem(menuItem); this.state.collapse = false; } else { this.state.collapse = !this.state.collapse; + resetWidth = true; } - this.view.call('LeftMenuToggleCollapse', { collapse: this.state.collapse }); + this.view.call('LeftMenuToggleCollapse', { + resetWidth, + collapse: this.state.collapse, + }); } /** 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 785795165418959bb6c204eb4f2534eb8517ea81..09ce895640c5b0d2a5d9603f2b12af88b9b78c0b 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 @@ -1,51 +1,58 @@ @include b(design-left-menu) { - display: flex; - width: 100%; + display: flex; + width: 100%; + height: 100%; + + @include e(side-container) { + width: 48px; height: 100%; + overflow: auto; + color: getCssVar(color, primary, disabled); + background: getCssVar(color, primary); - @include e(side-container) { - width: 48px; - height: 100%; - overflow: auto; - color: getCssVar(color, text, 3); - background: getCssVar(color, bg, 2); - - @include m(item){ - display: flex; - align-items: center; - justify-content: center; - width: 46px; - height: 48px; - font-size: getCssVar('font-size', 'header-3'); - cursor: pointer; - border-left: 2px solid getCssVar(color, primary); - - @include when(active) { - color: getCssVar(color, primary, active, text); - border-left: 2px solid getCssVar(color, primary, active, text); - } - - &:hover { - color: getCssVar(color, primary, hover, text); - } - } - } - - @include e(content-container) { - width: calc(100% - 48px); - height: 100%; - overflow: auto; - background-color: getCssVar(color, bg, 0); + @include m(item) { + display: flex; + align-items: center; + justify-content: center; + width: 46px; + height: 48px; + font-size: getCssVar('font-size', 'header-3'); + cursor: pointer; + border-left: 2px solid getCssVar(color, primary); + + @include when(active) { + color: getCssVar(color, primary, active, text); + border-left: 2px solid getCssVar(color, primary, active, text); + } + + &:hover { + color: getCssVar(color, primary, hover, text); + } + } + } + + @include e(content-container) { + width: calc(100% - 48px); + height: 100%; + overflow: auto; + background-color: getCssVar(color, bg, 0); + + @include when(collapse) { + display: none; + } - @include when(collapse) { - display: none; - } - - @include m(view) { - .#{bem(view)} { - background-color: getCssVar(color, bg, 0); - border-radius: 0; - } - } + @include m(view) { + .#{bem(view)} { + background-color: getCssVar(color, bg, 0); + border-radius: 0; + } } + } + + @include e(split) { + width: 2px; + height: 100%; + cursor: col-resize; + background-color: getCssVar(color, border); + } } \ No newline at end of file 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 a8359562d4a07d5b2700969778fdb8bc31622285..50b59864f1d36a95556a25558e6c6c00070a40fd 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,5 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { PropType, defineComponent, h, resolveComponent } from 'vue'; +import { PropType, defineComponent, h, ref, resolveComponent } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; import { IAppMenuItem, IPanelRawItem } from '@ibiz/model-core'; import { DesignLeftMenuController } from './design-left-menu.controller'; @@ -23,11 +23,56 @@ export default defineComponent({ // 控制器 const c = props.controller; + const split = ref(); + // 处理点击事件 const handleClick = (menuItem: IAppMenuItem, event: MouseEvent) => { c.handleMenuItemClick(menuItem, event); }; + // 处理菜单拖拽 + const handleMenuDrag = (evt: MouseEvent) => { + evt.preventDefault(); + const element = split.value; + + if (!element || !c.leftContainer) return; + + const shiftX = evt.clientX - element.getBoundingClientRect().left; + const totalWidth = document.getElementById('app')!.offsetWidth; + let animationFrameId: number; + // AnimationFrame 确保界面更新与浏览器的重绘周期对齐,从而提高性能和流畅度 + const moveAt = (pageX: number, pageY: number): void => { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + } + animationFrameId = requestAnimationFrame(() => { + const diffX = pageX - shiftX; + if ( + diffX >= (c.leftContainer!.model.width || 48) && + diffX <= totalWidth / 2 + ) { + c.leftContainer!.state.layout.width = `${(diffX / totalWidth) * 100}%`; + } + }); + }; + + moveAt(evt.pageX, evt.pageY); + + const onMouseMove = (event: MouseEvent): void => { + moveAt(event.pageX, event.pageY); + }; + + document.addEventListener('mousemove', onMouseMove); + + document.addEventListener( + 'mouseup', + () => { + document.removeEventListener('mousemove', onMouseMove); + }, + { once: true }, + ); + }; + // 绘制左侧菜单栏 const renderMenuItems = () => { return c.state.allMenuItems.map(menuItem => { @@ -69,7 +114,7 @@ export default defineComponent({ ); }; - return { c, ns, renderMenuItems, renderContentView }; + return { c, ns, split, renderMenuItems, renderContentView, handleMenuDrag }; }, render() { const contentClass = [ @@ -81,6 +126,13 @@ export default defineComponent({
{this.renderMenuItems()}
{this.renderContentView()}
+ {!this.c.state.collapse && ( +
+ )}
); }, diff --git a/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.controller.ts b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c131ab92d7c706086ecaf51c326b37db45be95a --- /dev/null +++ b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.controller.ts @@ -0,0 +1,117 @@ +import { + AppFuncCommand, + getControl, + PanelItemController, + ViewController, +} from '@ibiz-template/runtime'; +import { IAppMenu, IAppMenuItem, IPanelContainer } from '@ibiz/model-core'; +import { clone } from 'ramda'; +import { RuntimeModelError } from '@ibiz-template/core'; +import { DesignRightMenuState } from './design-right-menu.state'; + +/** + * 右侧菜单控制器 + * + * @export + * @class DesignRightMenuController + * @extends {PanelItemController} + */ +export class DesignRightMenuController extends PanelItemController { + /** + * 状态对象 + * + * @type {DesignRightMenuState} + * @memberof DesignRightMenuController + */ + declare state: DesignRightMenuState; + + /** + * 视图 + * + * @readonly + * @type {ViewController} + * @memberof DesignRightMenuController + */ + get view(): ViewController { + return this.panel.view as ViewController; + } + + /** + * 右侧菜单 + * + * @readonly + * @type {IAppMenu} + * @memberof DesignRightMenuController + */ + get rightSideMenu(): IAppMenu { + return getControl(this.view.model, 'rightsidemenu') as IAppMenu; + } + + /** + * 上下文 + * + * @type {IContext} + * @memberof DesignRightMenuController + */ + public context!: IContext; + + /** + * 视图参数 + * + * @type {IParams} + * @memberof DesignRightMenuController + */ + public params!: IParams; + + /** + * 初始化 + * + * @protected + * @return {*} {Promise} + * @memberof DesignRightMenuController + */ + protected async onInit(): Promise { + await super.onInit(); + await this.initState(); + } + + /** + * 初始化状态 + * + * @protected + * @return {*} {Promise} + * @memberof DesignRightMenuController + */ + protected async initState(): Promise { + this.context = clone(this.panel.context); + this.params = clone(this.panel.params); + this.state.allMenuItems = this.rightSideMenu?.appMenuItems || []; + } + + /** + * 处理菜单项点击 + * + * @author tony001 + * @date 2024-08-14 10:08:06 + * @param {IAppMenuItem} menuItem + * @param {MouseEvent} event + * @return {*} {Promise} + */ + public async handleMenuItemClick( + menuItem: IAppMenuItem, + event: MouseEvent, + ): Promise { + if (!menuItem.appFuncId) { + throw new RuntimeModelError( + menuItem, + ibiz.i18n.t('runtime.controller.control.menu.noConfigured'), + ); + } + await ibiz.commands.execute( + AppFuncCommand.TAG, + menuItem.appFuncId, + this.context, + this.params, + ); + } +} diff --git a/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.provider.ts b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..f16a1e0f66d22e0c1b4139241253d1fbc97fc51e --- /dev/null +++ b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.provider.ts @@ -0,0 +1,21 @@ +import { + IPanelItemController, + IPanelItemProvider, + IPanelController, +} from '@ibiz-template/runtime'; +import { IPanelItem } from '@ibiz/model-core'; +import { DesignRightMenuController } from './design-right-menu.controller'; + +export class DesignRightMenuProvider implements IPanelItemProvider { + component = 'IBizDesignRightMenu'; + + async createController( + panelItem: IPanelItem, + panel: IPanelController, + parent?: IPanelItemController, + ): Promise { + const c = new DesignRightMenuController(panelItem, panel, parent); + await c.init(); + return c; + } +} diff --git a/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.scss b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.scss new file mode 100644 index 0000000000000000000000000000000000000000..0029408b8bb36cad937a1b5cd082ad5d236f7472 --- /dev/null +++ b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.scss @@ -0,0 +1,29 @@ +@include b(design-right-menu) { + position: fixed; + right: getCssVar(spacing, tight); + bottom: getCssVar(spacing, tight); + z-index: 9999; + + @include e(button) { + width: 56px !important; + height: 56px !important; + font-size: getCssVar(font-size, header-3); + } + + @include e(menu-container) { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + @include when(hidden) { + display: none; + } + + @include m(item) { + width: 40px !important; + height: 40px !important; + margin: 0 0 getCssVar(spacing, tight) 0 !important; + font-size: getCssVar(font-size, header-5); + } + } +} diff --git a/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.state.ts b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.state.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0308dc5100586ee0ae18701f46499479558855f --- /dev/null +++ b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.state.ts @@ -0,0 +1,29 @@ +import { PanelItemState } from '@ibiz-template/runtime'; +import { IAppMenuItem } from '@ibiz/model-core'; + +export class DesignRightMenuState extends PanelItemState { + /** + * 激活菜单项 + * + * @type {(IAppMenuItem | undefined)} + * @memberof DesignRightMenuState + */ + activeMenuItem: IAppMenuItem | undefined = undefined; + + /** + * 激活视图模型 + * + * @type {(string | undefined)} + * @memberof DesignRightMenuState + */ + activeViewModelId: string | undefined = undefined; + + /** + * 所有菜单项 + * + * @author tony001 + * @date 2024-08-13 17:08:28 + * @type {IAppMenuItem[]} + */ + allMenuItems: IAppMenuItem[] = []; +} diff --git a/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.tsx b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..10ad0943e2f55f34b26a7322af94dc221aafa5d8 --- /dev/null +++ b/packages/layout-design/src/design-index-view/components/design-right-menu/design-right-menu.tsx @@ -0,0 +1,76 @@ +import { PropType, defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IPanelRawItem } from '@ibiz/model-core'; +import { DesignRightMenuController } from './design-right-menu.controller'; +import './design-right-menu.scss'; + +export default defineComponent({ + name: 'IBizDesignRightMenu', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + controller: { + type: DesignRightMenuController, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('design-right-menu'); + // 控制器 + const c = props.controller; + const showMenu = ref(false); + + // 处理点击事件 + const handleClick = (event: MouseEvent): void => { + showMenu.value = !showMenu.value; + }; + + // 绘制菜单栏 + const renderMenuItems = (): JSX.Element[] => { + return c.state.allMenuItems.map(menuItem => { + return ( + c.handleMenuItemClick(menuItem, e)} + > + + + ); + }); + }; + + return { c, ns, showMenu, handleClick, renderMenuItems }; + }, + render() { + return ( +
+
+ {this.renderMenuItems()} +
+ + {this.showMenu ? ( + + ) : ( + + )} + +
+ ); + }, +}); diff --git a/packages/layout-design/src/design-index-view/components/index.ts b/packages/layout-design/src/design-index-view/components/index.ts index bc4356d9bf3ebce3d8ddfdad93b2b4b489f97f80..23218c1a287c9d3287cb9c86d147177dc0bb076c 100644 --- a/packages/layout-design/src/design-index-view/components/index.ts +++ b/packages/layout-design/src/design-index-view/components/index.ts @@ -4,6 +4,8 @@ import { App } from 'vue'; import { registerPanelItemProvider } from '@ibiz-template/runtime'; import IBizDesignLeftMenu from './design-left-menu/design-left-menu'; import { DesignLeftMenuProvider } from './design-left-menu/design-left-menu.provider'; +import IBizDesignRightMenu from './design-right-menu/design-right-menu'; +import { DesignRightMenuProvider } from './design-right-menu/design-right-menu.provider'; export default { install(app: App) { @@ -13,5 +15,10 @@ export default { 'RAWITEM_LEFT_SIDE_MENU', () => new DesignLeftMenuProvider(), ); + app.component('IBizDesignRightMenu', IBizDesignRightMenu); + registerPanelItemProvider( + 'RAWITEM_RIGHT_SIDE_MENU', + () => new DesignRightMenuProvider(), + ); }, }; diff --git a/packages/layout-design/src/design-index-view/views/design-index-view/design-index-view.engine.ts b/packages/layout-design/src/design-index-view/views/design-index-view/design-index-view.engine.ts index 835a9512cf1c5c3af7705cd5dc65c259d5f0380f..0d1434fcb78fcdfa2c18a355f84d219aabdcf230 100644 --- a/packages/layout-design/src/design-index-view/views/design-index-view/design-index-view.engine.ts +++ b/packages/layout-design/src/design-index-view/views/design-index-view/design-index-view.engine.ts @@ -68,7 +68,7 @@ export class DesignIndexViewEngine extends IndexViewEngine { if (leftContainer) { if (this.view.state.isCollapse) { leftContainer.state.layout.width = '48px'; - } else { + } else if (_args.resetWidth) { leftContainer.state.layout.width = `${leftContainer.model.width}px`; } }