From fe36d51bb94d2ca130ca117660cb624cebe75884 Mon Sep 17 00:00:00 2001
From: lijisanxiong <1518062161@qq.com>
Date: Tue, 29 Jul 2025 20:11:54 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E6=89=A9?=
=?UTF-8?q?=E5=B1=95=E8=8F=9C=E5=8D=95=E5=B8=B8=E8=A7=84=E6=A8=A1=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 1 +
.../common-extend-menu/common-extend-menu.tsx | 24 +-
.../extend-menu-base/extend-menu-base.util.ts | 64 ++-
.../extend-standard-menu.scss | 447 ++++++++++++++++++
.../extend-standard-menu.tsx | 321 +++++++++++++
5 files changed, 845 insertions(+), 12 deletions(-)
create mode 100644 src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.scss
create mode 100644 src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.tsx
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d5d4caf4..d5a40f68d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
- 泳道看板新增分组隐藏功能
- 看板识别enableFullScreen(启用全屏)和 enableGroupHidden(启用分组隐藏)参数
- 新增卡片新建功能
+- 新增扩展菜单常规模式
### Fixed
diff --git a/src/panel-component/app-extend-menu/extend-menu-base/common-extend-menu/common-extend-menu.tsx b/src/panel-component/app-extend-menu/extend-menu-base/common-extend-menu/common-extend-menu.tsx
index e9c0e9d1c..9c05c2bfe 100644
--- a/src/panel-component/app-extend-menu/extend-menu-base/common-extend-menu/common-extend-menu.tsx
+++ b/src/panel-component/app-extend-menu/extend-menu-base/common-extend-menu/common-extend-menu.tsx
@@ -6,6 +6,7 @@ import { IAppMenuItemProvider } from '@ibiz-template/runtime';
import { useNamespace } from '@ibiz-template/vue3-util';
import './common-extend-menu.scss';
import { ExtendButtonMenu } from '../extend-button-menu/extend-button-menu';
+import { ExtendStandardMenu } from '../extend-standard-menu/extend-standard-menu';
/**
* 扩展基础菜单
@@ -17,6 +18,7 @@ export const CommonExtendMenu = defineComponent({
name: 'IBizCommonExtendMenu',
components: {
ExtendButtonMenu,
+ ExtendStandardMenu,
},
props: {
/**
@@ -94,9 +96,20 @@ export const CommonExtendMenu = defineComponent({
return { ns, handleMenuItemClick };
},
render() {
- return (
-
-
+ );
+ if (this.renderMode?.toLocaleUpperCase() === 'MENU') {
+ content = (
+
-
- );
+ );
+ }
+ return {content}
;
},
});
diff --git a/src/panel-component/app-extend-menu/extend-menu-base/extend-menu-base.util.ts b/src/panel-component/app-extend-menu/extend-menu-base/extend-menu-base.util.ts
index 7c8d02ec6..cf7207ac9 100644
--- a/src/panel-component/app-extend-menu/extend-menu-base/extend-menu-base.util.ts
+++ b/src/panel-component/app-extend-menu/extend-menu-base/extend-menu-base.util.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-use-before-define */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-plusplus */
@@ -38,10 +39,10 @@ export interface IExtendMenuProps {
* @description 菜单项绘制数据
*
* @export
- * @interface IButtonMenuItem
+ * @interface IMenuBaseItem
* @extends {IAppMenuItem}
*/
-export interface IButtonMenuItem extends IAppMenuItem {
+export interface IMenuBaseItem extends IAppMenuItem {
/** 数据层级 */
level: number;
/** 主键 */
@@ -51,9 +52,18 @@ export interface IButtonMenuItem extends IAppMenuItem {
/** 父主键 */
parentId?: string;
/** 子项数据集合 */
- children?: IButtonMenuItem[];
+ children?: IMenuBaseItem[];
}
+/**
+ * @description 按钮菜单项绘制数据
+ *
+ * @export
+ * @interface IButtonMenuItem
+ * @extends {IAppMenuItem}
+ */
+export interface IButtonMenuItem extends IMenuBaseItem {}
+
/**
* @description 菜单绘制通用参数
*
@@ -78,7 +88,7 @@ export interface IMenuRenderParams {
*/
export interface IMenuItemParams extends IMenuRenderParams {
/** 菜单项 */
- menu: IButtonMenuItem;
+ menu: IMenuBaseItem;
}
/**
@@ -107,6 +117,46 @@ export interface IMenuContentParams extends IMenuRenderParams {
handleMenuItemMouseLeave: (_menu: IButtonMenuItem, event: MouseEvent) => void;
}
+/**
+ * @description 绘制常规菜单项参数
+ *
+ * @export
+ * @interface IStandardMenuItemParams
+ * @extends {IMenuRenderParams}
+ */
+export interface IStandardMenuItemParams extends IMenuRenderParams {
+ /** 菜单项 */
+ menu: IMenuBaseItem;
+ /** 是否为首次绘制 */
+ isFirst: boolean;
+ /** 是否水平折叠收起菜单 */
+ collapse: boolean;
+}
+
+/**
+ * @description 绘制常规菜单的参数
+ *
+ * @export
+ * @interface IStandardMenuContentParams
+ * @extends {IMenuContentParams}
+ */
+export interface IStandardMenuContentParams extends IMenuRenderParams {
+ /** 是否支持布局 */
+ isLayout: boolean;
+ /** 菜单方向 */
+ position: string;
+ /** 是否水平折叠收起菜单(仅在 menuAlign 为 vertical 时可用) */
+ collapse: boolean;
+ /** 刷新key */
+ refreshKey: string;
+ /** 菜单项绘制数据集合 */
+ menus: IMenuBaseItem[];
+ /** 菜单布局模型 */
+ menuLayout?: ILayout;
+ /** 菜单选中 */
+ handleMenuSelect: (_id: string, event: MouseEvent) => void;
+}
+
/**
* @description 用于构建级联菜单的弹出层控制逻辑
*
@@ -611,15 +661,15 @@ export function useBorderLayout(
* 递归生成菜单数据,递给 element 的 Menu 组件
*
* @param {AppMenuItemModel[]} items
- * @return {*} {IButtonMenuItem[]}
+ * @return {*} {IMenuBaseItem[]}
*/
export function getMenus(
items: IAppMenuItem[],
_parentItem?: IAppMenuItem,
level = 0,
-): IButtonMenuItem[] {
+): IMenuBaseItem[] {
return items.map(item => {
- const data: IButtonMenuItem = {
+ const data: IMenuBaseItem = {
...item,
value: item.id,
label: item.caption,
diff --git a/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.scss b/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.scss
new file mode 100644
index 000000000..2afd3f5a8
--- /dev/null
+++ b/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.scss
@@ -0,0 +1,447 @@
+/* extend-standard-menu 菜单 start */
+$extend-standard-menu: (
+ 'collapse-item-hover-color': getCssVar(color, primary, hover, text),
+ 'collapse-item-padding': getCssVar(spacing, base),
+ 'icon-width': getCssVar(width-icon, large),
+ 'icon-height': getCssVar(width-icon, large),
+ 'icon-margin': 0 getCssVar(spacing, tight) 0 0,
+ 'text-margin': 0 0 0 getCssVar(spacing, tight),
+ 'icon-position': calc(getCssVar(spacing, base) - 2px),
+ 'icon-size': getCssVar(width-icon,medium),
+ 'collapse-height': getCssVar(spacing, super-loose),
+ 'scorll-color': getCssVar(color, scroll, menu),
+ 'horizontal-height': 50px,
+ 'hover-color': getCssVar(color, primary, hover, text),
+ 'hover-bg-color': getCssVar(color, primary, hover),
+ 'active-color': getCssVar(color, primary, active, text),
+ 'active-bg-color': getCssVar(color, primary, active),
+ 'color': getCssVar(color, text, menu),
+);
+
+$extend-standard-menu-item: (
+ 'selected-border-color': transparent,
+ 'selected-color': getCssVar(color, primary, active, text),
+ 'selected-bg-color': getCssVar(color, primary, active),
+ 'hover-color': getCssVar(color, primary, hover, text),
+ 'hover-bg-color': getCssVar(color, primary, hover),
+ 'padding': getCssVar(spacing, tight),
+ 'border-radius': getCssVar(border-radius, small),
+ 'border': 2px solid getCssVar(color, text, 1),
+ 'font-size': getCssVar(font-size, header-6),
+ 'height': 42px,
+ 'gap': 2px,
+ 'color': getCssVar(color, text, menu),
+ 'separator-color': getCssVar(color, scroll, menu),
+ 'horizontal-selected-color': getCssVar(color, primary, active, text),
+ 'horizontal-selected-bg-color': getCssVar(color, primary, active),
+ 'horizontal-hover-color': getCssVar(color, primary, hover, text),
+ 'horizontal-hover-bg-color': getCssVar(color, primary, hover),
+ 'horizontal-color': getCssVar(color, text, menu),
+ 'horizontal-font-size': getCssVar(font-size, header-5),
+ 'horizontal-height': 100%,
+ 'horizontal-border-radius': 0,
+ 'horizontal-padding': 0 getCssVar(spacing, base-loose),
+ 'horizontal-padding-right': calc(getCssVar(width-icon, large) + getCssVar(spacing, base-loose)),
+ 'horizontal-popup-height': 40px,
+);
+
+/* extend-standard-menu 菜单 end */
+
+// 垂直菜单项样式
+@mixin menu-item-vertical-style {
+ @include flex(row, flex-start, center);
+
+ --el-menu-base-level-padding: #{getCssVar('extend-standard-menu-item', 'padding')};
+
+ width: 100%;
+ height: getCssVar('extend-standard-menu-item', 'height');
+ font-size: getCssVar('extend-standard-menu-item', 'font-size');
+ color: getCssVar('extend-standard-menu-item', 'color');
+ white-space: nowrap;
+ border-radius: getCssVar('extend-standard-menu-item', 'border-radius');
+
+ &.is-active {
+ color: getCssVar('extend-standard-menu-item', 'selected-color') !important;
+ background-color: getCssVar('extend-standard-menu-item', 'selected-bg-color');
+ }
+
+ &:hover {
+ color: getCssVar('extend-standard-menu-item', 'hover-color');
+ background-color: getCssVar('extend-standard-menu-item', 'hover-bg-color');
+ }
+}
+
+// 水平菜单项样式
+@mixin menu-item-horizontal-style {
+ height: getCssVar('extend-standard-menu-item', 'horizontal-height');
+ padding: getCssVar('extend-standard-menu-item', 'horizontal-padding');
+ font-size: getCssVar('extend-standard-menu-item', 'horizontal-font-size');
+ line-height: getCssVar('extend-standard-menu-item', 'horizontal-height');
+ color: getCssVar('extend-standard-menu-item', 'horizontal-color');
+ white-space: nowrap;
+ border-width: 0;
+ border-radius: getCssVar('extend-standard-menu-item', 'horizontal-border-radius');
+
+ &:hover {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-hover-color');
+ background-color: getCssVar(
+ 'extend-standard-menu-item',
+ 'horizontal-hover-bg-color'
+ );
+ }
+
+ &.is-active {
+ // element-plus 给了 !important,所以这里也要加上
+ color: getCssVar('extend-standard-menu-item', 'selected-color') !important;
+ background-color: getCssVar('extend-standard-menu-item', 'horizontal-selected-bg-color');
+ border-top: getCssVar('extend-standard-menu-item', 'border');
+ }
+}
+
+// 收缩菜单项样式
+@mixin menu-collapse-item-style {
+ @include flex-center;
+
+ padding: getCssVar(spacing, none);
+ margin-bottom: getCssVar(spacing, tight);
+
+}
+
+// 菜单项选中样式
+@mixin menu-item-selected-style {
+ color: getCssVar('extend-standard-menu-item', 'selected-color');
+ background-color: getCssVar('extend-standard-menu-item', 'selected-bg-color');
+}
+
+// 直接内容菜单项样式
+@mixin raw-item-menu-style {
+ --el-menu-hover-text-color: #{getCssVar('extend-standard-menu-item', 'color')};
+ --el-menu-hover-bg-color: transparent;
+ @include set-component-css-var('extend-standard-menu-item', $extend-standard-menu-item);
+ .#{bem('extend-standard-menu', 'rawitem')} {
+ min-height: getCssVar('extend-standard-menu-item', 'height');
+ line-height: getCssVar('extend-standard-menu-item', 'height');
+ .#{bem('rawitem')} {
+ @include flex(row, flex-start, center);
+
+ font-size: getCssVar('extend-standard-menu-item', 'font-size');
+ color: getCssVar('extend-standard-menu-item', 'color');
+
+ &>.#{bem('icon')} {
+ display: flex;
+ align-items: center;
+ width: getCssVar('extend-standard-menu', 'icon-width');
+ height: getCssVar('extend-standard-menu', 'icon-height');
+ font-size: getCssVar('extend-standard-menu','icon-size');
+ }
+
+ * {
+ @include utils-ellipsis;
+ }
+ }
+ }
+}
+
+@include b(extend-standard-menu) {
+ width: 100%;
+ height: 100%;
+
+ @include e('content') {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ background-color: getCssVar(color, primary);
+
+ @include set-component-css-var('extend-standard-menu', $extend-standard-menu);
+ @include set-component-css-var('extend-standard-menu-item', $extend-standard-menu-item);
+
+ &>.el-menu {
+ height: 100%;
+ padding: getCssVar(spacing, none) getCssVar(spacing, base);
+ overflow: hidden auto;
+ border-right: 0;
+ @include raw-item-menu-style;
+ }
+
+ .el-sub-menu {
+ .el-sub-menu__icon-arrow {
+ right: 10px;
+ width: getCssVar('extend-standard-menu', 'icon-width');
+ height: getCssVar('extend-standard-menu', 'icon-height');
+ margin-top: -10px;
+ font-size: getCssVar('extend-standard-menu', 'icon-width');
+ }
+
+ .el-menu-item {
+ padding: calc(getCssVar('extend-standard-menu', 'item-padding') * 0.875)
+ calc(getCssVar('extend-standard-menu', 'item-padding') * 2.5);
+ }
+
+ .el-sub-menu__title >.#{bem('extend-standard-menu', 'counter')} {
+ right: 30px;
+ }
+ }
+
+ // 垂直
+ .el-menu--vertical {
+ width: 100%;
+
+ // 菜单项样式
+ .el-menu-item,
+ .el-sub-menu__title {
+ @include menu-item-vertical-style;
+ }
+
+ .el-menu-item + .el-menu-item {
+ margin-top: getCssVar('extend-standard-menu-item', 'gap');
+ }
+
+ .el-sub-menu + .el-menu-item {
+ margin-top: getCssVar('extend-standard-menu-item', 'gap');
+ }
+
+ .el-sub-menu + .el-sub-menu {
+ margin-top: getCssVar('extend-standard-menu-item', 'gap');
+ }
+
+ .el-menu-item + .el-sub-menu {
+ margin-top: getCssVar('extend-standard-menu-item', 'gap');
+ }
+
+ .el-sub-menu__title + .el-menu {
+ margin-top: getCssVar('extend-standard-menu-item', 'gap');
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: getCssVar('extend-standard-menu', 'scorll-color');
+ }
+ }
+
+ // 水平
+ .el-menu--horizontal {
+ align-items: center;
+ height: 100%;
+ padding: getCssVar(spacing, none) getCssVar(spacing, base-tight);
+
+ // 水平菜单默认为ellipsis模式,无需滚动条
+ overflow-x: hidden;
+ border-bottom: none;
+
+ // 菜单项样式
+ .el-menu-item,
+ .el-sub-menu__title {
+ @include menu-item-horizontal-style;
+
+ .el-sub-menu__icon-arrow {
+ right: getCssVar('extend-standard-menu', 'icon-position');
+ }
+ }
+
+ .el-sub-menu {
+ height: getCssVar('extend-standard-menu-item', 'horizontal-height');
+
+ >.el-sub-menu__title {
+ padding-right: getCssVar('extend-standard-menu-item', 'horizontal-padding-right');
+ }
+ }
+
+ // 副菜单悬浮
+ > .el-sub-menu:hover .el-sub-menu__title {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-hover-color');
+ background-color: getCssVar(
+ 'extend-standard-menu-item',
+ 'horizontal-hover-bg-color'
+ );
+ }
+
+ // 副菜单激活
+ > .el-sub-menu.is-active .el-sub-menu__title {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-selected-color');
+ background-color: getCssVar('extend-standard-menu-item', 'horizontal-selected-bg-color');
+ border-top: getCssVar('extend-standard-menu-item', 'border');
+ border-bottom: 0;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: getCssVar('extend-standard-menu', 'scorll-color');
+ }
+
+ .el-divider--vertical {
+ border-color: getCssVar(extend-standard-menu-item, separator-color);
+ }
+ }
+ @include when('horizontal') {
+ #{getCssVarName(color, text, 0)}: getCssVar(color, primary, text);
+ #{getCssVarName(color, text, 1)}: getCssVar(color, primary, text);
+ #{getCssVarName(color, text, 2)}: getCssVar(color, primary, text);
+ #{getCssVarName(color, text, 3)}: getCssVar(color, primary, text);
+ width: 100%;
+ height: getCssVar('extend-standard-menu', 'horizontal-height');
+ }
+
+ @include when('bottom') {
+ .el-menu--horizontal {
+ .el-menu-item.is-active,
+ .el-sub-menu.is-active .el-sub-menu__title {
+ border-top: 0;
+ border-bottom: getCssVar('extend-standard-menu-item', 'border');
+ }
+ }
+ }
+
+ @include when('collapse') {
+ &>.el-menu {
+ padding: getCssVar(spacing, tight);
+ }
+ }
+
+ .el-menu {
+ #{getCssVarName(color, primary, hover)}: getCssVar(extend-standard-menu, active, bg, color);
+ #{getCssVarName(color, primary, hover, text)}: getCssVar(extend-standard-menu, active, color);
+ #{getCssVarName(color, primary, active)}: getCssVar(extend-standard-menu, hover, bg, color);
+ #{getCssVarName(color, primary, active, text)}: getCssVar(extend-standard-menu, hover, color);
+
+ --el-button-text-color: #{getCssVar(extend-standard-menu, color)};
+ }
+ }
+
+ // 图标样式
+ @include e(icon) {
+ @include flex(row, center, center);
+
+ width: getCssVar('extend-standard-menu', 'icon-width');
+ height: getCssVar('extend-standard-menu', 'icon-height');
+ font-size: getCssVar('extend-standard-menu','icon-size');
+
+ &+.#{bem('extend-standard-menu__caption')} {
+ margin: getCssVar('extend-standard-menu', 'text-margin');
+ }
+ }
+
+ // 文字样式
+ @include e('caption') {
+ @include utils-ellipsis;
+ }
+}
+
+@include b(extend-standard-menu-popup-container){
+ @include raw-item-menu-style;
+
+ // 收缩时菜单项样式
+ // 以及水平菜单悬浮出来菜单样式
+ &.el-menu--popup-container {
+ @include set-component-css-var('extend-standard-menu', $extend-standard-menu);
+ @include set-component-css-var('extend-standard-menu-item', $extend-standard-menu-item);
+
+ border: none;
+ box-shadow: getCssVar('shadow', 'elevated');
+
+ .el-menu--popup {
+ max-height: 100vh;
+ padding: getCssVar('spacing', 'extra-tight');
+ overflow: auto;
+ background-color: getCssVar('color', 'primary');
+
+ .el-menu-item,
+ .el-sub-menu__title {
+ @include flex(row, flex-start, center);
+
+ width: 100%;
+ height: getCssVar('extend-standard-menu-item', 'horizontal-popup-height');
+ font-size: getCssVar('extend-standard-menu-item', 'font-size');
+ color: getCssVar('extend-standard-menu-item', 'horizontal-color');
+ white-space: nowrap;
+ border-radius: getCssVar('extend-standard-menu-item', 'horizontal-border-radius');
+
+ &.is-active {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-selected-color');
+ background-color: getCssVar('extend-standard-menu-item', 'horizontal-selected-bg-color');
+ }
+
+ &:hover {
+ color: getCssVar('extend-standard-menu-item', 'hover-color');
+ background-color: getCssVar('extend-standard-menu-item', 'hover-bg-color');
+ }
+
+ .el-icon {
+ display: none;
+ }
+ }
+ }
+ }
+
+ // 计数器样式
+ .#{bem('extend-standard-menu', 'counter')} {
+ position: absolute;
+ top: 50%;
+ right: 5px;
+ height: 20px;
+ padding: getCssVar('spacing', 'none') getCssVar('spacing', 'tight');
+ line-height: initial;
+ color: getCssVar('color', danger, text);
+ background-color: getCssVar('color', 'danger');
+ border-radius: getCssVar('border','radius','medium');
+ transform: translateY(-50%);
+ }
+
+}
+
+// 分割线
+.#{bem('extend-standard-menu', 'separator', 'horizontal')} {
+ margin: getCssVar(spacing, tight) 0;
+ border-color: getCssVar(extend-standard-menu-item, separator-color);
+}
+
+.el-menu--horizontal.el-menu--popup-container:has(.#{bem(extend-standard-menu, item)},.#{bem(extend-standard-menu-submenu)}) {
+
+ @include raw-item-menu-style;
+
+ @include set-component-css-var('extend-standard-menu', $extend-standard-menu);
+ @include set-component-css-var('extend-standard-menu-item', $extend-standard-menu-item);
+
+ border: none;
+ box-shadow: getCssVar('shadow', 'elevated');
+
+ .el-menu--popup {
+ padding: getCssVar('spacing', 'extra-tight');
+ background-color: getCssVar('color', 'primary');
+
+ .el-menu-item,
+ .el-sub-menu__title {
+ @include flex(row, flex-start, center);
+
+ width: 100%;
+ height: getCssVar('extend-standard-menu-item', 'horizontal-popup-height');
+ font-size: getCssVar('extend-standard-menu-item', 'font-size');
+ color: getCssVar('extend-standard-menu-item', 'horizontal-color');
+ white-space: nowrap;
+ border-radius: getCssVar('extend-standard-menu-item', 'horizontal-border-radius');
+
+ &.is-active {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-selected-color');
+ background-color: getCssVar('extend-standard-menu-item', 'horizontal-selected-bg-color');
+ }
+
+ &:hover {
+ color: getCssVar('extend-standard-menu-item', 'hover-color');
+ background-color: getCssVar('extend-standard-menu-item', 'hover-bg-color');
+ }
+
+ .el-icon {
+ display: none;
+ }
+ }
+ }
+
+ .el-menu .el-sub-menu.is-active>.el-sub-menu__title {
+ color: getCssVar('extend-standard-menu-item', 'horizontal-selected-color');
+ }
+
+ // 分割线
+ .#{bem('extend-standard-menu', 'separator', 'vertical')} {
+ width: 100%;
+ height: 1px;
+ margin: getCssVar(spacing, tight) 0;
+ border-top: 1px getCssVar(extend-standard-menu-item, separator-color) solid;
+ }
+}
\ No newline at end of file
diff --git a/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.tsx b/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.tsx
new file mode 100644
index 000000000..4e2dfc078
--- /dev/null
+++ b/src/panel-component/app-extend-menu/extend-menu-base/extend-standard-menu/extend-standard-menu.tsx
@@ -0,0 +1,321 @@
+import { ref, VNode, PropType, defineComponent, computed } from 'vue';
+import { IAppMenuItem, ILayout } from '@ibiz/model-core';
+import { showTitle } from '@ibiz-template/core';
+import { useNamespace } from '@ibiz-template/vue3-util';
+import { IAppMenuItemProvider } from '@ibiz-template/runtime';
+import { createUUID } from 'qx-util';
+import {
+ findMenuItem,
+ getMenus,
+ IStandardMenuContentParams,
+ IStandardMenuItemParams,
+ IMenuBaseItem,
+} from '../extend-menu-base.util';
+import './extend-standard-menu.scss';
+
+const ellipsisSvg = (): VNode => {
+ return ;
+};
+
+/**
+ * 绘制菜单项
+ * @param {IStandardMenuItemParams} _params
+ * @returns {*}
+ */
+function renderMenuItem(_params: IStandardMenuItemParams): VNode | undefined {
+ const { isFirst, menu, collapse, ns, menuAlign, menuItemsState } = _params;
+ // TODO 是否显示状态值待定
+ if (!menu.id || (menuItemsState && !menuItemsState[menu.id]?.visible)) {
+ return;
+ }
+
+ if (menu.itemType === 'MENUITEM') {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let content: any;
+ if (!(isFirst && collapse)) {
+ content = [
+ menu.sysImage ? (
+
+ ) : null,
+ {menu.caption},
+ ];
+ } else {
+ content = [
+ menu.sysImage ? (
+
+ ) : (
+ {menu.caption?.slice(0, 1)}
+ ),
+ ];
+ }
+
+ return !(isFirst && collapse) ? (
+
+ {content}
+
+ ) : (
+
+
+ {content}
+
+
+ );
+ }
+ if (menu.itemType === 'SEPERATOR') {
+ const direction =
+ menuAlign === 'horizontal' && isFirst ? 'vertical' : 'horizontal';
+ return (
+
+ );
+ }
+ if (menu.itemType === 'RAWITEM') {
+ return (
+
+
+
+ );
+ }
+}
+
+/**
+ * 绘制子菜单
+ * @param {IStandardMenuItemParams} _params
+ * @returns {*}
+ */
+function renderSubmenu(_params: IStandardMenuItemParams): VNode | undefined {
+ const { isFirst, menu, collapse, ns, menuAlign, menuItemsState } = _params;
+ if (!menu.id || (menuItemsState && !menuItemsState[menu.id]?.visible)) {
+ return;
+ }
+ return (
+
+ );
+}
+
+/**
+ * 绘制菜单内容
+ *
+ * @param {IStandardMenuContentParams} _params
+ * @return {*}
+ */
+function renderMenuContent(_params: IStandardMenuContentParams): VNode {
+ const {
+ refreshKey,
+ ns,
+ collapse,
+ menuAlign,
+ position,
+ menus,
+ menuItemsState,
+ handleMenuSelect,
+ } = _params;
+
+ return (
+
+ ellipsisSvg()}
+ ellipsis={menuAlign === 'horizontal'}
+ >
+ {{
+ default: () => {
+ return menus.map(item => {
+ if (item.children && item.children.length > 0) {
+ return renderSubmenu({
+ isFirst: true,
+ menu: item,
+ collapse,
+ ns,
+ menuAlign,
+ menuItemsState,
+ });
+ }
+ return renderMenuItem({
+ isFirst: true,
+ menu: item,
+ collapse,
+ ns,
+ menuAlign,
+ menuItemsState,
+ });
+ });
+ },
+ }}
+
+
+ );
+}
+
+export const ExtendStandardMenu = defineComponent({
+ name: 'IBizExtendStandardMenu',
+ props: {
+ items: { type: Object as PropType, required: true },
+ menuItemsState: {
+ type: Object as PropType<{
+ [p: string]: { visible: boolean; permitted: boolean };
+ }>,
+ required: true,
+ },
+ providers: {
+ type: Object as PropType<{ [key: string]: IAppMenuItemProvider }>,
+ required: true,
+ },
+ position: {
+ type: String as PropType<'LEFT' | 'RIGHT' | 'TOP' | 'BOTTOM'>,
+ required: true,
+ },
+ layoutMode: {
+ type: String as PropType<
+ 'TABLE' | 'TABLE_12COL' | 'TABLE_24COL' | 'FLEX' | 'BORDER' | 'ABSOLUTE'
+ >,
+ required: true,
+ },
+ layout: { type: Object as PropType },
+ },
+ emits: {
+ /**
+ * @description 项点击事件
+ */
+ menuItemClick: (_item: IAppMenuItem, _event: MouseEvent) => true,
+ },
+ setup(props, { emit }) {
+ const ns = useNamespace('extend-standard-menu');
+ const defaultMenuRef = ref();
+ const refreshKey = ref(createUUID());
+ const collapse = ref(false);
+ const menuAlign = computed(() =>
+ ['TOP', 'BOTTOM'].includes(props.position) ? 'horizontal' : 'vertical',
+ );
+ const menus = ref(getMenus(props.items));
+ const handleMenuSelect = async (
+ _id: string,
+ _event: MouseEvent,
+ ): Promise => {
+ const menuItem = findMenuItem(_id, props.items);
+ if (!menuItem?.appFuncId) {
+ ibiz.log.warn(
+ ibiz.i18n.t('runtime.controller.control.menu.noConfigured'),
+ );
+ return;
+ }
+ emit('menuItemClick', menuItem!, _event);
+ };
+
+ return {
+ ns,
+ defaultMenuRef,
+ refreshKey,
+ collapse,
+ menuAlign,
+ menus,
+ handleMenuSelect,
+ };
+ },
+ render() {
+ return (
+
+ {renderMenuContent({
+ isLayout: false,
+ position: this.position,
+ refreshKey: this.refreshKey,
+ ns: this.ns,
+ collapse: this.collapse,
+ menuAlign: this.menuAlign,
+ menus: this.menus,
+ menuLayout: this.layout,
+ menuItemsState: this.menuItemsState,
+ handleMenuSelect: this.handleMenuSelect,
+ })}
+
+ );
+ },
+});
--
Gitee
From 0146217d11d1189ee3664d92305a1def86d57583 Mon Sep 17 00:00:00 2001
From: lijisanxiong <1518062161@qq.com>
Date: Tue, 29 Jul 2025 20:23:30 +0800
Subject: [PATCH 2/2] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E9=A1=B9=E8=A1=8C=E4=B8=BA=E7=BB=98=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 1 +
src/control/list/list.scss | 25 +++++++++++++++++++++++++
src/control/list/list.tsx | 24 +++++++++++++++++++++++-
3 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5a40f68d..6040a5cea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
- 看板识别enableFullScreen(启用全屏)和 enableGroupHidden(启用分组隐藏)参数
- 新增卡片新建功能
- 新增扩展菜单常规模式
+- 新增列表项行为绘制
### Fixed
diff --git a/src/control/list/list.scss b/src/control/list/list.scss
index 3c2940a35..78512708d 100644
--- a/src/control/list/list.scss
+++ b/src/control/list/list.scss
@@ -20,6 +20,31 @@ $control-list-group-style2: (
color: getCssVar(control-list, text-color);
cursor: pointer;
background-color: getCssVar(control-list, item-bg-color);
+
+ @include e(right) {
+ padding: 0 getCssVar(spacing, extra,tight);
+ @include m(actions) {
+ display: flex;
+ justify-content: center;
+ .#{bem(action-toolbar, item)} {
+ margin: 0;
+ }
+ .#{bem(action-toolbar, item, label)} {
+ &.is-has-caption {
+ margin: 0;
+ }
+ }
+ }
+ }
+
+ &:has(.#{bem(control-list-item__right)}) {
+ display: flex;
+ justify-content: space-between;
+
+ .#{bem(control-list-item__caption)} {
+ flex: 1;
+ }
+ }
}
@include b(control-list) {
diff --git a/src/control/list/list.tsx b/src/control/list/list.tsx
index 0c5ce7505..571d8c979 100644
--- a/src/control/list/list.tsx
+++ b/src/control/list/list.tsx
@@ -216,8 +216,24 @@ export const ListControl = defineComponent({
);
};
+ // 绘制项行为
+ const renderItemAction = (item: IData): VNode => {
+ return (
+ => c.onActionClick(detail, item, event)}
+ >
+ );
+ };
+
// 绘制默认列表项
const renderDefaultItem = (item: IData): VNode => {
+ const actionModel = c.getOptItemModel();
return (
=> c.onRowClick(item)}
onDblclick={(): Promise
=> c.onDbRowClick(item)}
>
- {`${isNil(item.srfmajortext) ? '' : item.srfmajortext}`}
+ {`${
+ isNil(item.srfmajortext) ? '' : item.srfmajortext
+ }`}
+
+ {actionModel.length ? (
+ {renderItemAction(item)}
+ ) : null}
);
};
--
Gitee