From d1147a76585f767349f9161329f8a65be8f015aa Mon Sep 17 00:00:00 2001
From: ShineKOT <1917095344@qq.com>
Date: Sun, 29 Sep 2024 14:31:08 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=20=E8=8F=9C=E5=8D=95=E9=83=A8?=
=?UTF-8?q?=E4=BB=B6=E6=94=AF=E6=8C=81=E5=88=86=E5=89=B2=E9=A1=B9=E6=89=A9?=
=?UTF-8?q?=E5=B1=95=E6=A8=A1=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 3 +++
src/control/app-menu/app-menu.scss | 12 +++++++++++-
src/control/app-menu/app-menu.tsx | 7 +++++++
3 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 625c0c36e..84a54dec0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@
并且此项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/).
## [Unreleased]
+### Added
+
+- 菜单部件支持分割项扩展模式
### Fixed
diff --git a/src/control/app-menu/app-menu.scss b/src/control/app-menu/app-menu.scss
index 87b8e29c0..788d746c7 100644
--- a/src/control/app-menu/app-menu.scss
+++ b/src/control/app-menu/app-menu.scss
@@ -39,7 +39,8 @@ $control-appmenu-item: (
// 垂直菜单项样式
@mixin menu-item-vertical-style {
@include flex(row, flex-start, center);
-
+ --el-menu-item-height: #{getCssVar('control-appmenu-item', 'height')};
+ --el-menu-sub-item-height: #{getCssVar('control-appmenu-item', 'height')};
--el-menu-base-level-padding: #{getCssVar('control-appmenu-item', 'padding')};
width: 100%;
@@ -126,6 +127,12 @@ $control-appmenu-item: (
@include set-component-css-var('control-appmenu', $control-appmenu);
@include set-component-css-var('control-appmenu-item', $control-appmenu-item);
+ @include e(separator) {
+ @include m(span-mode) {
+ flex-grow: 1;
+ }
+ }
+
>.el-menu {
height: 100%;
padding: getCssVar(spacing, none) getCssVar(spacing, tight);
@@ -153,6 +160,8 @@ $control-appmenu-item: (
// 垂直
.el-menu--vertical {
width: 100%;
+ display: flex;
+ flex-direction: column;
// 菜单项样式
.el-menu-item,
@@ -163,6 +172,7 @@ $control-appmenu-item: (
// 水平
.el-menu--horizontal {
+ display: flex;
& > * + *{
padding-right: getCssVar(spacing, base);
}
diff --git a/src/control/app-menu/app-menu.tsx b/src/control/app-menu/app-menu.tsx
index db8d9c4ba..d59b639b0 100644
--- a/src/control/app-menu/app-menu.tsx
+++ b/src/control/app-menu/app-menu.tsx
@@ -45,6 +45,7 @@ function getMenus(items: IAppMenuItem[]): IData[] {
disabled: !item.appFuncId,
tooltip: item.tooltip,
itemType: item.itemType,
+ spanMode: item.spanMode,
sysCss: item.sysCss,
};
if (item.appMenuItems?.length) {
@@ -196,6 +197,12 @@ function renderMenuItem(
);
}
if (menu.itemType === 'SEPERATOR') {
+ const spanModeItem = c.model.appMenuItems?.find(
+ menuItem => menuItem.itemType === 'SEPERATOR' && menuItem.spanMode,
+ );
+ if (menu.key === spanModeItem?.id) {
+ return
;
+ }
const direction =
c.view.model.mainMenuAlign === 'TOP' && isFirst
? 'vertical'
--
Gitee
From 89180a4d65cd363641b298f667686af38aa4a25d Mon Sep 17 00:00:00 2001
From: ShineKOT <1917095344@qq.com>
Date: Mon, 30 Sep 2024 18:57:49 +0800
Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=83=A8?=
=?UTF-8?q?=E4=BB=B6=E5=86=85=E7=BD=AE=E5=AF=BC=E8=88=AA=E7=BB=84=E4=BB=B6?=
=?UTF-8?q?=E5=8F=8A=E6=96=B0=E5=A2=9E=E8=A1=A8=E6=A0=BC=E5=86=85=E7=BD=AE?=
=?UTF-8?q?=E5=AF=BC=E8=88=AA=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../control-navigation.scss | 15 +
.../control-navigation/control-navigation.tsx | 122 +++++++
.../control-navigation.util.ts | 326 ++++++++++++++++++
src/common/index.ts | 2 +
src/control/grid/grid/grid.tsx | 23 +-
5 files changed, 476 insertions(+), 12 deletions(-)
create mode 100644 src/common/control-navigation/control-navigation.scss
create mode 100644 src/common/control-navigation/control-navigation.tsx
create mode 100644 src/common/control-navigation/control-navigation.util.ts
diff --git a/src/common/control-navigation/control-navigation.scss b/src/common/control-navigation/control-navigation.scss
new file mode 100644
index 000000000..843c1447b
--- /dev/null
+++ b/src/common/control-navigation/control-navigation.scss
@@ -0,0 +1,15 @@
+@include b(control-navigation) {
+ width: 100%;
+ height: 100%;
+ @include e(nav-control) {
+ position: relative;
+ @include m(icon) {
+ top: 0;
+ right: 0;
+ z-index: 1;
+ cursor: pointer;
+ position: absolute;
+ color: getCssVar(color, primary);
+ }
+ }
+}
diff --git a/src/common/control-navigation/control-navigation.tsx b/src/common/control-navigation/control-navigation.tsx
new file mode 100644
index 000000000..85dd65b7a
--- /dev/null
+++ b/src/common/control-navigation/control-navigation.tsx
@@ -0,0 +1,122 @@
+/* eslint-disable no-nested-ternary */
+import { EventBase, MDControlController } from '@ibiz-template/runtime';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import { defineComponent, h, PropType, resolveComponent } from 'vue';
+import { useControlNav } from './control-navigation.util';
+import './control-navigation.scss';
+
+/**
+ * 部件内容导航组件
+ */
+export const IBizControlNavigation = defineComponent({
+ name: 'IBizControlNavigation',
+ props: {
+ controller: {
+ type: Object as PropType,
+ required: true,
+ },
+ },
+ setup(props, { slots }) {
+ const ns = useNamespace('control-navigation');
+
+ const {
+ style,
+ enableNav,
+ splitMode,
+ splitValue,
+ showNavView,
+ showNavIcon,
+ navViewMsg,
+ } = useControlNav(props.controller);
+
+ const onViewCreated = (event: EventBase): void => {
+ ibiz.log.debug(props.controller.name, 'onViewCreated', event);
+ };
+
+ const onShowViewChange = (event: MouseEvent) => {
+ event.stopPropagation();
+ event.preventDefault();
+ showNavView.value = !showNavView.value;
+ };
+
+ const renderNavControl = () => {
+ return (
+
+ {slots.default?.()}
+ {showNavIcon.value ? (
+
+ {!showNavView.value ? (
+
+ ) : (
+
+ )}
+
+ ) : null}
+
+ );
+ };
+
+ const renderNavView = () => {
+ if (navViewMsg.value) {
+ return h(resolveComponent('IBizViewShell'), {
+ ...navViewMsg.value,
+ class: ns.e('nav-view'),
+ onCreated: onViewCreated,
+ });
+ }
+ };
+
+ return {
+ ns,
+ style,
+ enableNav,
+ splitMode,
+ splitValue,
+ navViewMsg,
+ showNavView,
+ renderNavView,
+ renderNavControl,
+ };
+ },
+ render() {
+ return (
+
+ {this.enableNav ? (
+ this.showNavView ? (
+
+ {{
+ left: () => this.renderNavControl(),
+ right: () => this.renderNavView(),
+ top: () => this.renderNavControl(),
+ bottom: () => this.renderNavView(),
+ }}
+
+ ) : (
+ this.renderNavControl()
+ )
+ ) : (
+ this.$slots.default?.()
+ )}
+
+ );
+ },
+});
diff --git a/src/common/control-navigation/control-navigation.util.ts b/src/common/control-navigation/control-navigation.util.ts
new file mode 100644
index 000000000..0546c38be
--- /dev/null
+++ b/src/common/control-navigation/control-navigation.util.ts
@@ -0,0 +1,326 @@
+import {
+ IDER1N,
+ IDETree,
+ IDETreeNode,
+ INavigatable,
+ IControlNavigatable,
+} from '@ibiz/model-core';
+import {
+ INavViewMsg,
+ calcNavParams,
+ IMDControlEvent,
+ MDControlController,
+ calcDeCodeNameById,
+} from '@ibiz-template/runtime';
+import { CSSProperties, Ref, ref } from 'vue';
+import { RuntimeError } from '@ibiz-template/core';
+
+/**
+ * 获取导航视图样式
+ *
+ * @param {IControlNavigatable} control
+ * @return {*} {CSSProperties}
+ */
+function getNavViewStyle(control: IControlNavigatable): CSSProperties {
+ const {
+ navViewWidth,
+ navViewHeight,
+ navViewMinWidth,
+ navViewMaxWidth,
+ navViewMinHeight,
+ navViewMaxHeight,
+ } = control;
+ return {
+ width: navViewWidth ? `${navViewWidth}px` : undefined,
+ height: navViewHeight ? `${navViewHeight}px` : undefined,
+ minWidth: navViewMinWidth ? `${navViewMinWidth}px` : undefined,
+ maxWidth: navViewMaxWidth ? `${navViewMaxWidth}px` : undefined,
+ minHeight: navViewMinHeight ? `${navViewMinHeight}px` : undefined,
+ maxHeight: navViewMaxHeight ? `${navViewMaxHeight}px` : undefined,
+ };
+}
+
+/**
+ * 解析导航参数
+ *
+ * @param {(INavigatable & { appDataEntityId?: string })} XDataModel
+ * @param {IData} data
+ * @param {IContext} context
+ * @param {IParams} params
+ * @return {*} {{ context: IContext; params: IParams }}
+ */
+function prepareNavParams(
+ XDataModel: INavigatable & { appDataEntityId?: string },
+ data: IData,
+ context: IContext,
+ params: IParams,
+): { context: IContext; params: IParams } {
+ const {
+ navDER,
+ navFilter,
+ navigateContexts,
+ navigateParams,
+ appDataEntityId,
+ } = XDataModel;
+ const model = {
+ deName: appDataEntityId ? calcDeCodeNameById(appDataEntityId) : undefined,
+ navFilter,
+ pickupDEFName: (navDER as IDER1N)?.pickupDEFName,
+ navContexts: navigateContexts,
+ navParams: navigateParams,
+ };
+ const originParams = {
+ context,
+ params,
+ data,
+ };
+ const { resultContext, resultParams } = calcNavParams(model, originParams);
+ const tempContext = Object.assign(context.clone(), resultContext);
+ const tempParams = { ...resultParams };
+ return { context: tempContext, params: tempParams };
+}
+
+/**
+ * 获取树节点模型
+ *
+ * @param {string} nodeId
+ * @param {IDETree} model
+ * @return {*} {(IDETreeNode | undefined)}
+ */
+function getNodeModel(nodeId: string, model: IDETree): IDETreeNode | undefined {
+ const { detreeNodes } = model;
+ let nodeModel: IDETreeNode | undefined;
+ if (detreeNodes) {
+ detreeNodes.forEach(node => {
+ if (node.id === nodeId) {
+ nodeModel = node;
+ }
+ });
+ }
+ return nodeModel;
+}
+
+/**
+ * 获取有导航视图的节点模型标识集合
+ *
+ * @param {IDETree} model
+ * @return {*} {string[]}
+ */
+function getNavNodeModelIds(model: IDETree): string[] {
+ const { detreeNodes } = model;
+ const navNodeModelIds: string[] = [];
+ detreeNodes?.forEach(node => {
+ if (node.navAppViewId) {
+ navNodeModelIds.push(node.id!);
+ }
+ });
+ return navNodeModelIds;
+}
+
+/**
+ * 获取导航视图信息
+ *
+ * @param {(INavigatable & { appDataEntityId?: string })} model
+ * @param {IData} data
+ * @param {IContext} context
+ * @param {IParams} params
+ * @param {string} viewModelId
+ * @param {string} [keyName='srfkey']
+ * @return {*} {INavViewMsg}
+ */
+function getNavViewMsg(
+ model: INavigatable & { appDataEntityId?: string },
+ data: IData,
+ context: IContext,
+ params: IParams,
+ viewModelId: string,
+ keyName: string = 'srfkey',
+): INavViewMsg {
+ const result = prepareNavParams(model, data, context, params);
+ return {
+ key: data[keyName],
+ context: result.context,
+ params: result.params,
+ viewId: viewModelId,
+ };
+}
+
+/**
+ * 部件导航
+ *
+ * @export
+ * @param {MDControlController} controller
+ * @return {*} {({
+ * style: CSSProperties;
+ * splitMode: Ref<'horizontal' | 'vertical'>;
+ * splitValue: Ref;
+ * showNavView: Ref;
+ * showNavIcon: Ref;
+ * navViewMsg: Ref;
+ * })}
+ */
+export function useControlNav(controller: MDControlController): {
+ enableNav: Ref;
+ style: CSSProperties;
+ splitMode: Ref<'horizontal' | 'vertical'>;
+ splitValue: Ref;
+ showNavView: Ref;
+ showNavIcon: Ref;
+ navViewMsg: Ref;
+} {
+ const { navViewPos, navViewShowMode } =
+ controller.model as IControlNavigatable;
+ /**
+ * 启用内置导航
+ */
+ const enableNav: Ref = ref(
+ navViewShowMode !== undefined &&
+ ![undefined, 'NONE'].includes('navViewPos'),
+ );
+ /**
+ * 导航样式
+ */
+ const style = getNavViewStyle(controller.model);
+ /**
+ * 分割值
+ */
+ const splitValue: Ref = ref(0.5);
+ /**
+ * 是否显示导航视图
+ */
+ const showNavView: Ref = ref(navViewShowMode !== 1);
+ /**
+ * 是否显示导航图标
+ */
+ const showNavIcon: Ref = ref(navViewShowMode !== 2);
+ /**
+ * 分割模式
+ */
+ const splitMode: Ref<'horizontal' | 'vertical'> = ref(
+ ['BOTTOM', 'ANY_BOTTOM'].includes(navViewPos!) ? 'vertical' : 'horizontal',
+ );
+ /**
+ * 导航视图信息
+ */
+ const navViewMsg: Ref = ref();
+ /**
+ * 有导航视图的节点模型标识集合
+ */
+ const navNodeModelIds: string[] =
+ controller.model.controlType === 'TREEVIEW'
+ ? getNavNodeModelIds(controller.model)
+ : [];
+ /**
+ * 导航栈数据
+ */
+ const navStack: string[] = [];
+
+ /**
+ * 导航数据改变
+ *
+ * @param {IMDControlEvent['onNavDataChange']['event']} event
+ */
+ const onNavDataChange = (
+ event: IMDControlEvent['onNavDataChange']['event'],
+ ) => {
+ const { navData, context, params } = event;
+ let keyName = 'srfkey';
+ // 设置导航视图信息
+ if (['GRID', 'DATAVIEW', 'LIST'].includes(controller.model.controlType!)) {
+ const viewModelId = (controller.model as INavigatable).navAppViewId;
+ if (viewModelId) {
+ navViewMsg.value = getNavViewMsg(
+ controller.model,
+ navData,
+ context,
+ params,
+ viewModelId,
+ keyName,
+ );
+ }
+ } else if (controller.model.controlType === 'TREEVIEW') {
+ keyName = '_id';
+ const nodeModel = getNodeModel(navData._nodeId, controller.model);
+ if (!nodeModel) {
+ throw new RuntimeError(
+ ibiz.i18n.t('runtime.controller.control.expBar.noFindNodeModel', {
+ nodeId: navData._nodeId,
+ }),
+ );
+ }
+ if (nodeModel.navAppViewId) {
+ const deData = navData._deData || navData;
+ navViewMsg.value = getNavViewMsg(
+ nodeModel,
+ deData,
+ context,
+ params,
+ nodeModel.navAppViewId,
+ keyName,
+ );
+ }
+ }
+ // 添加栈数据
+ navStack.unshift(navData[keyName]);
+ };
+
+ /**
+ * 设置默认导航
+ *
+ */
+ const setDefaultNavData = () => {
+ const { items } = controller.state;
+ const setNavData = (data: IData | undefined) => {
+ if (data) {
+ controller.setSelection([data]);
+ controller.setNavData(data);
+ } else {
+ navViewMsg.value = undefined;
+ }
+ };
+ const getStackData = (keyName: string = 'srfkey') => {
+ const preNav = navStack.find(nav =>
+ items.find(item => nav === item[keyName]),
+ );
+ return preNav ? items.find(item => preNav === item[keyName]) : items[0];
+ };
+
+ if (['GRID', 'DATAVIEW', 'LIST'].includes(controller.model.controlType!)) {
+ const stack = getStackData();
+ setNavData(stack);
+ } else if (controller.model.controlType === 'TREEVIEW') {
+ const data = controller.state.items.find(node => {
+ return navNodeModelIds.includes(node._nodeId);
+ });
+ setNavData(data);
+ }
+ };
+
+ controller.evt.on('onNavDataChange', evt => {
+ if (enableNav.value) {
+ onNavDataChange(evt);
+ }
+ });
+
+ controller.evt.on('onLoadSuccess', () => {
+ if (enableNav.value) {
+ setDefaultNavData();
+ }
+ });
+
+ controller.evt.on('onRemoveSuccess', () => {
+ if (enableNav.value) {
+ setDefaultNavData();
+ }
+ });
+
+ return {
+ enableNav,
+ style,
+ splitMode,
+ splitValue,
+ showNavView,
+ showNavIcon,
+ navViewMsg,
+ };
+}
diff --git a/src/common/index.ts b/src/common/index.ts
index af17f22a1..c95c3a121 100644
--- a/src/common/index.ts
+++ b/src/common/index.ts
@@ -36,6 +36,7 @@ import { IBizPqlEditor } from './pql-editor/pql-editor';
import { IBizCustomFilterCondition } from './custom-filter-condition/custom-filter-condition';
import { IBizAnchorContainer } from './anchor-container/anchor-container';
import { IBizButtonList } from './button-list/button-list';
+import { IBizControlNavigation } from './control-navigation/control-navigation';
export * from './col/col';
export * from './row/row';
@@ -90,6 +91,7 @@ export const IBizCommonComponents = {
v.component(IBizCustomFilterCondition.name, IBizCustomFilterCondition);
v.component(IBizAnchorContainer.name, IBizAnchorContainer);
v.component(IBizButtonList.name, IBizButtonList);
+ v.component(IBizControlNavigation.name, IBizControlNavigation);
},
};
diff --git a/src/control/grid/grid/grid.tsx b/src/control/grid/grid/grid.tsx
index fd9cb197e..5f88c0655 100644
--- a/src/control/grid/grid/grid.tsx
+++ b/src/control/grid/grid/grid.tsx
@@ -410,10 +410,9 @@ export const GridControl = defineComponent({
controller={this.c}
style={this.headerCssVars}
>
- {
+
- }
+ {this.c.model.enableCustomized && !this.c.state.hideHeader && (
+
+
+
+ )}
+ {this.renderBatchToolBar()}
+
{this.c.state.enablePagingBar && (
)}
- {this.c.model.enableCustomized && !this.c.state.hideHeader && (
-
-
-
- )}
- {this.renderBatchToolBar()}
);
},
--
Gitee