diff --git a/docs/docs/api/commonUI.md b/docs/docs/api/commonUI.md
index f3afe1fc2..975e3caaa 100644
--- a/docs/docs/api/commonUI.md
+++ b/docs/docs/api/commonUI.md
@@ -48,8 +48,70 @@ CommonUI API 是一个专为低代码引擎设计的组件 UI 库,使用它开
| condition | 显示条件函数
Function to determine display condition | (nodes: IPublicModelNode[]) => boolean (optional) | |
| disabled | 禁用条件函数,可选
Function to determine disabled condition, optional | (nodes: IPublicModelNode[]) => boolean (optional) | |
+**ContextMenu 示例**
+
+```typescript
+const App = () => {
+ const menuItems: IPublicTypeContextMenuAction[] = [
+ {
+ name: 'a',
+ title: '选项 1',
+ actions: () => console.log('选项 1 被点击'),
+ },
+ {
+ name: 'b',
+ title: '选项 2',
+ actions: () => console.log('选项 2 被点击'),
+ },
+ ];
+
+ const ContextMenu = ctx.commonUI.ContextMenu;
+
+ return (
+
+ );
+};
+
+export default App;
+```
+
+**ContextMenu.create 示例**
+
+```typescript
+const App = () => {
+ const menuItems: IPublicTypeContextMenuAction[] = [
+ {
+ name: 'a',
+ title: '选项 1',
+ actions: () => console.log('选项 1 被点击'),
+ },
+ {
+ name: 'b',
+ title: '选项 2',
+ actions: () => console.log('选项 2 被点击'),
+ },
+ ];
+
+ const ContextMenu = ctx.commonUI.ContextMenu;
+
+ return (
+
+
{
+ ContextMenu.create(menuItems, e);
+ }}>点击这里
+
+ );
+};
+
+export default App;
+```
### Balloon
+
详细文档: [Balloon Documentation](https://fusion.design/pc/component/balloon)
### Breadcrumb
diff --git a/docs/docs/api/material.md b/docs/docs/api/material.md
index c83935b97..faf4f1edc 100644
--- a/docs/docs/api/material.md
+++ b/docs/docs/api/material.md
@@ -250,6 +250,33 @@ material.modifyBuiltinComponentAction('remove', (action) => {
addContextMenuOption(action: IPublicTypeContextMenuAction): void;
```
+示例
+
+```typescript
+import { IPublicEnumContextMenuType } from '@alilc/lowcode-types';
+
+material.addContextMenuOption({
+ name: 'parentItem',
+ title: 'Parent Item',
+ condition: (node) => true,
+ items: [
+ {
+ name: 'childItem1',
+ title: 'Child Item 1',
+ action: (node) => console.log('Child Item 1 clicked', node),
+ condition: (node) => true
+ },
+ // 分割线
+ {
+ type: IPublicEnumContextMenuType.SEPARATOR
+ name: 'separator.1'
+ }
+ // 更多子菜单项...
+ ]
+});
+
+```
+
#### removeContextMenuOption
删除特定右键菜单项
@@ -274,7 +301,26 @@ removeContextMenuOption(name: string): void;
adjustContextMenuLayout(fn: (actions: IPublicTypeContextMenuItem[]) => IPublicTypeContextMenuItem[]): void;
```
+**示例**
+
+通过 adjustContextMenuLayout 补充分割线
+
+```typescript
+material.adjustContextMenuLayout((actions: IPublicTypeContextMenuAction) => {
+ const names = ['a', 'b'];
+ const newActions = [];
+ actions.forEach(d => {
+ newActions.push(d);
+ if (names.include(d.name)) {
+ newActions.push({ type: 'separator' })
+ }
+ });
+ return newActions
+})
+```
+
### 物料元数据
+
#### getComponentMeta
获取指定名称的物料元数据
diff --git a/packages/designer/src/context-menu-actions.ts b/packages/designer/src/context-menu-actions.ts
index 5b44055b4..c3bad37da 100644
--- a/packages/designer/src/context-menu-actions.ts
+++ b/packages/designer/src/context-menu-actions.ts
@@ -1,4 +1,4 @@
-import { IPublicTypeContextMenuAction, IPublicEnumContextMenuType, IPublicTypeContextMenuItem, IPublicApiMaterial } from '@alilc/lowcode-types';
+import { IPublicTypeContextMenuAction, IPublicEnumContextMenuType, IPublicTypeContextMenuItem, IPublicApiMaterial, IPublicModelPluginContext } from '@alilc/lowcode-types';
import { IDesigner, INode } from './designer';
import { createContextMenu, parseContextMenuAsReactNode, parseContextMenuProperties, uniqueId } from '@alilc/lowcode-utils';
import { Menu } from '@alifd/next';
@@ -48,6 +48,7 @@ export class GlobalContextMenuActions {
event.preventDefault();
const actions: IPublicTypeContextMenuAction[] = [];
+ let contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value;
this.contextMenuActionsMap.forEach((contextMenu) => {
actions.push(...contextMenu.actions);
});
@@ -57,11 +58,13 @@ export class GlobalContextMenuActions {
const destroy = () => {
destroyFn?.();
};
+ const pluginContext: IPublicModelPluginContext = contextMenu.designer.editor.get('pluginContext') as IPublicModelPluginContext;
const menus: IPublicTypeContextMenuItem[] = parseContextMenuProperties(actions, {
nodes: [],
destroy,
event,
+ pluginContext,
});
if (!menus.length) {
@@ -73,6 +76,7 @@ export class GlobalContextMenuActions {
const menuNode = parseContextMenuAsReactNode(layoutMenu, {
destroy,
nodes: [],
+ pluginContext,
});
const target = event.target;
@@ -160,10 +164,13 @@ export class ContextMenuActions implements IContextMenuActions {
destroyFn?.();
};
+ const pluginContext: IPublicModelPluginContext = this.designer.editor.get('pluginContext') as IPublicModelPluginContext;
+
const menus: IPublicTypeContextMenuItem[] = parseContextMenuProperties(actions, {
nodes: nodes.map(d => designer.shellModelFactory.createNode(d)!),
destroy,
event,
+ pluginContext,
});
if (!menus.length) {
@@ -175,7 +182,7 @@ export class ContextMenuActions implements IContextMenuActions {
const menuNode = parseContextMenuAsReactNode(layoutMenu, {
destroy,
nodes: nodes.map(d => designer.shellModelFactory.createNode(d)!),
- designer,
+ pluginContext,
});
destroyFn = createContextMenu(menuNode, {
diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts
index 29b4a7f03..3ccfdf51d 100644
--- a/packages/engine/src/engine-core.ts
+++ b/packages/engine/src/engine-core.ts
@@ -115,12 +115,11 @@ const innerSetters = new InnerSetters();
const setters = new Setters(innerSetters);
const material = new Material(editor);
-const commonUI = new CommonUI();
+const commonUI = new CommonUI(editor);
editor.set('project', project);
editor.set('setters' as any, setters);
editor.set('material', material);
editor.set('innerHotkey', innerHotkey);
-editor.set('commonUI' as any, commonUI);
const config = new Config(engineConfig);
const event = new Event(commonEvent, { prefix: 'common' });
const logger = new Logger({ level: 'warn', bizName: 'common' });
@@ -147,6 +146,7 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.commonUI = commonUI;
context.registerLevel = IPublicEnumPluginRegisterLevel.Default;
context.isPluginRegisteredInWorkspace = false;
+ editor.set('pluginContext', context);
},
};
diff --git a/packages/engine/src/inner-plugins/default-context-menu.ts b/packages/engine/src/inner-plugins/default-context-menu.ts
index 9d1336b34..db3d54a0c 100644
--- a/packages/engine/src/inner-plugins/default-context-menu.ts
+++ b/packages/engine/src/inner-plugins/default-context-menu.ts
@@ -5,12 +5,11 @@ import {
IPublicModelNode,
IPublicModelPluginContext,
IPublicTypeDragNodeDataObject,
- IPublicTypeI18nData,
IPublicTypeNodeSchema,
} from '@alilc/lowcode-types';
-import { isI18nData, isProjectSchema } from '@alilc/lowcode-utils';
+import { isProjectSchema } from '@alilc/lowcode-utils';
import { Notification } from '@alifd/next';
-import { intl, getLocale } from '../locale';
+import { intl } from '../locale';
function getNodesSchema(nodes: IPublicModelNode[]) {
const componentsTree = nodes.map((node) => node?.exportSchema(IPublicEnumTransformStage.Clone));
@@ -18,15 +17,6 @@ function getNodesSchema(nodes: IPublicModelNode[]) {
return data;
}
-function getIntlStr(data: string | IPublicTypeI18nData) {
- if (!isI18nData(data)) {
- return data;
- }
-
- const locale = getLocale();
- return data[locale] || data['zh-CN'] || data['zh_CN'] || data['en-US'] || data['en_US'] || '';
-}
-
async function getClipboardText(): Promise {
return new Promise((resolve, reject) => {
// 使用 Clipboard API 读取剪贴板内容
@@ -61,8 +51,9 @@ async function getClipboardText(): Promise {
}
export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
- const { material, canvas } = ctx;
+ const { material, canvas, common } = ctx;
const { clipboard } = canvas;
+ const { intl: utilsIntl } = common.utils;
return {
init() {
@@ -150,7 +141,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
});
if (canAddNodes.length === 0) {
Notification.open({
- content: `${nodeSchema.map(d => getIntlStr(d.title || d.componentName)).join(',')}等组件无法放置到${getIntlStr(parent.title || parent.componentName as any)}内`,
+ content: `${nodeSchema.map(d => utilsIntl(d.title || d.componentName)).join(',')}等组件无法放置到${utilsIntl(parent.title || parent.componentName as any)}内`,
type: 'error',
});
return;
@@ -198,7 +189,7 @@ export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
});
if (canAddNodes.length === 0) {
Notification.open({
- content: `${nodeSchema.map(d => getIntlStr(d.title || d.componentName)).join(',')}等组件无法放置到${getIntlStr(node.title || node.componentName as any)}内`,
+ content: `${nodeSchema.map(d => utilsIntl(d.title || d.componentName)).join(',')}等组件无法放置到${utilsIntl(node.title || node.componentName as any)}内`,
type: 'error',
});
return;
diff --git a/packages/shell/src/api/commonUI.ts b/packages/shell/src/api/commonUI.tsx
similarity index 56%
rename from packages/shell/src/api/commonUI.ts
rename to packages/shell/src/api/commonUI.tsx
index 9f40afa11..cb4b7ae38 100644
--- a/packages/shell/src/api/commonUI.ts
+++ b/packages/shell/src/api/commonUI.tsx
@@ -1,12 +1,16 @@
-import { IPublicApiCommonUI } from '@alilc/lowcode-types';
+import { IPublicApiCommonUI, IPublicModelPluginContext, IPublicTypeContextMenuAction } from '@alilc/lowcode-types';
import {
+ IEditor,
Tip as InnerTip,
Title as InnerTitle,
} from '@alilc/lowcode-editor-core';
import { Balloon, Breadcrumb, Button, Card, Checkbox, DatePicker, Dialog, Dropdown, Form, Icon, Input, Loading, Message, Overlay, Pagination, Radio, Search, Select, SplitButton, Step, Switch, Tab, Table, Tree, TreeSelect, Upload, Divider } from '@alifd/next';
import { ContextMenu } from '../components/context-menu';
+import { editorSymbol } from '../symbols';
export class CommonUI implements IPublicApiCommonUI {
+ [editorSymbol]: IEditor;
+
Balloon = Balloon;
Breadcrumb = Breadcrumb;
Button = Button;
@@ -35,13 +39,29 @@ export class CommonUI implements IPublicApiCommonUI {
Upload = Upload;
Divider = Divider;
+ constructor(editor: IEditor) {
+ this[editorSymbol] = editor;
+ }
+
get Tip() {
return InnerTip;
}
get Title() {
return InnerTitle;
}
+
get ContextMenu() {
- return ContextMenu;
+ const editor = this[editorSymbol];
+ const innerContextMenu = (props: any) => {
+ const pluginContext: IPublicModelPluginContext = editor.get('pluginContext') as IPublicModelPluginContext;
+ return ;
+ };
+
+ innerContextMenu.create = (menus: IPublicTypeContextMenuAction[], event: MouseEvent) => {
+ const pluginContext: IPublicModelPluginContext = editor.get('pluginContext') as IPublicModelPluginContext;
+ return ContextMenu.create(pluginContext, menus, event);
+ };
+
+ return innerContextMenu;
}
}
diff --git a/packages/shell/src/components/context-menu.tsx b/packages/shell/src/components/context-menu.tsx
index acefbebd2..d2e10abbf 100644
--- a/packages/shell/src/components/context-menu.tsx
+++ b/packages/shell/src/components/context-menu.tsx
@@ -1,11 +1,12 @@
import { createContextMenu, parseContextMenuAsReactNode, parseContextMenuProperties } from '@alilc/lowcode-utils';
import { engineConfig } from '@alilc/lowcode-editor-core';
-import { IPublicTypeContextMenuAction } from '@alilc/lowcode-types';
+import { IPublicModelPluginContext, IPublicTypeContextMenuAction } from '@alilc/lowcode-types';
import React from 'react';
-export function ContextMenu({ children, menus }: {
+export function ContextMenu({ children, menus, pluginContext }: {
menus: IPublicTypeContextMenuAction[];
children: React.ReactElement[] | React.ReactElement;
+ pluginContext: IPublicModelPluginContext;
}): React.ReactElement> {
if (!engineConfig.get('enableContextMenu')) {
return (
@@ -23,7 +24,10 @@ export function ContextMenu({ children, menus }: {
};
const children: React.ReactNode[] = parseContextMenuAsReactNode(parseContextMenuProperties(menus, {
destroy,
- }));
+ pluginContext,
+ }), {
+ pluginContext,
+ });
if (!children?.length) {
return;
@@ -44,4 +48,20 @@ export function ContextMenu({ children, menus }: {
return (
<>{childrenWithContextMenu}>
);
-}
\ No newline at end of file
+}
+
+ContextMenu.create = (pluginContext: IPublicModelPluginContext, menus: IPublicTypeContextMenuAction[], event: MouseEvent) => {
+ const children: React.ReactNode[] = parseContextMenuAsReactNode(parseContextMenuProperties(menus, {
+ pluginContext,
+ }), {
+ pluginContext,
+ });
+
+ if (!children?.length) {
+ return;
+ }
+
+ return createContextMenu(children, {
+ event,
+ });
+};
\ No newline at end of file
diff --git a/packages/utils/src/context-menu.scss b/packages/utils/src/context-menu.scss
index 0bcf39d15..366d03e45 100644
--- a/packages/utils/src/context-menu.scss
+++ b/packages/utils/src/context-menu.scss
@@ -1,32 +1,42 @@
-.context-menu-tree-wrap {
+.engine-context-menu-tree-wrap {
position: relative;
padding: 4px 10px 4px 32px;
}
-.context-menu-tree-children {
+.engine-context-menu-tree-children {
margin-left: 8px;
line-height: 24px;
}
-.context-menu-tree-bg {
- position: absolute;
- left: 0;
- right: 0;
- cursor: pointer;
+.engine-context-menu-item {
+ .engine-context-menu-text {
+ color: var(--color-text);
+ }
+
+ &:hover {
+ .engine-context-menu-text {
+ color: var(--color-title);
+ }
+ }
+
+ &.disbale {
+ .engine-context-menu-text {
+ color: var(--color-text-disabled);
+ }
+ }
}
-.context-menu-tree-bg-inner {
- position: absolute;
- height: 24px;
- top: -24px;
- width: 100%;
+.engine-context-menu-title {
+ color: var(--color-text);
+ cursor: pointer;
&:hover {
background-color: var(--color-block-background-light);
+ color: var(--color-title);
}
}
-.context-menu-tree-selected-icon {
+.engine-context-menu-tree-selecte-icon {
position: absolute;
left: 10px;
color: var(--color-icon-active);
diff --git a/packages/utils/src/context-menu.tsx b/packages/utils/src/context-menu.tsx
index 1816f5b52..0f157e438 100644
--- a/packages/utils/src/context-menu.tsx
+++ b/packages/utils/src/context-menu.tsx
@@ -1,7 +1,7 @@
import { Menu, Icon } from '@alifd/next';
-import { IDesigner } from '@alilc/lowcode-designer';
-import { IPublicEnumContextMenuType, IPublicModelNode, IPublicTypeContextMenuAction, IPublicTypeContextMenuItem } from '@alilc/lowcode-types';
+import { IPublicEnumContextMenuType, IPublicModelNode, IPublicModelPluginContext, IPublicTypeContextMenuAction, IPublicTypeContextMenuItem } from '@alilc/lowcode-types';
import { Logger } from '@alilc/lowcode-utils';
+import classNames from 'classnames';
import React from 'react';
import './context-menu.scss';
@@ -10,43 +10,51 @@ const { Item, Divider, PopupItem } = Menu;
const MAX_LEVEL = 2;
+interface IOptions {
+ nodes?: IPublicModelNode[] | null;
+ destroy?: Function;
+ pluginContext: IPublicModelPluginContext;
+}
+
const Tree = (props: {
- node?: IPublicModelNode;
+ node?: IPublicModelNode | null;
children?: React.ReactNode;
- options: {
- nodes?: IPublicModelNode[] | null;
- destroy?: Function;
- designer?: IDesigner;
- };
+ options: IOptions;
}) => {
const { node } = props;
if (!node) {
return (
- { props.children }
+ { props.children }
);
}
- const commonUI = props.options.designer?.editor?.get('commonUI');
-
- const Title = commonUI?.Title;
+ const { common } = props.options.pluginContext || {};
+ const { intl } = common?.utils || {};
+ const indent = node.zLevel * 8 + 32;
+ const style = {
+ paddingLeft: indent,
+ marginLeft: -indent,
+ marginRight: -10,
+ paddingRight: 10,
+ };
return (
- {props.options.nodes?.[0].id === node.id ? () : null}
-
{
+ props.options.destroy?.();
+ node.select();
+ }}
+ style={style}
+ >
+ {props.options.nodes?.[0].id === node.id ? () : null}
+ {intl(node.title)}
+
+
-
{
- props.options.destroy?.();
- node.select();
- }}
- >
-
-
{ props.children }
@@ -55,11 +63,10 @@ const Tree = (props: {
let destroyFn: Function | undefined;
-export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[], options: {
- nodes?: IPublicModelNode[] | null;
- destroy?: Function;
- designer?: IDesigner;
-} = {}): React.ReactNode[] {
+export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[], options: IOptions): React.ReactNode[] {
+ const { common } = options.pluginContext || {};
+ const { intl = (title: any) => title } = common?.utils || {};
+
const children: React.ReactNode[] = [];
menus.forEach((menu, index) => {
if (menu.type === IPublicEnumContextMenuType.SEPARATOR) {
@@ -70,14 +77,33 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
if (menu.type === IPublicEnumContextMenuType.MENU_ITEM) {
if (menu.items && menu.items.length) {
children.push((
-
+ {intl(menu.title)}}
+ >
));
} else {
- children.push((- {menu.title}
));
+ children.push((
+ -
+
+ {intl(menu.title)}
+
+
+ ));
}
}
@@ -91,9 +117,7 @@ export function parseContextMenuAsReactNode(menus: IPublicTypeContextMenuItem[],
return children;
}
-export function parseContextMenuProperties(menus: (IPublicTypeContextMenuAction | Omit)[], options: {
- nodes?: IPublicModelNode[] | null;
- destroy?: Function;
+export function parseContextMenuProperties(menus: (IPublicTypeContextMenuAction | Omit)[], options: IOptions & {
event?: MouseEvent;
}, level = 1): IPublicTypeContextMenuItem[] {
destroyFn?.();
@@ -156,7 +180,6 @@ function getMenuItemHeight() {
}
const root = document.documentElement;
const styles = getComputedStyle(root);
- // Access the value of the CSS variable
const menuItemHeight = styles.getPropertyValue('--context-menu-item-height').trim();
cachedMenuItemHeight = menuItemHeight;
diff --git a/packages/workspace/src/context/base-context.ts b/packages/workspace/src/context/base-context.ts
index e090d1e37..2f6154788 100644
--- a/packages/workspace/src/context/base-context.ts
+++ b/packages/workspace/src/context/base-context.ts
@@ -128,13 +128,12 @@ export class BasicContext implements IBasicContext {
const logger = getLogger({ level: 'warn', bizName: 'common' });
const skeleton = new Skeleton(innerSkeleton, 'any', true);
const canvas = new Canvas(editor, true);
- const commonUI = new CommonUI();
+ const commonUI = new CommonUI(editor);
editor.set('setters', setters);
editor.set('project', project);
editor.set('material', material);
editor.set('hotkey', hotkey);
editor.set('innerHotkey', innerHotkey);
- editor.set('commonUI' as any, commonUI);
this.innerSetters = innerSetters;
this.innerSkeleton = innerSkeleton;
this.skeleton = skeleton;
@@ -175,6 +174,7 @@ export class BasicContext implements IBasicContext {
}
context.registerLevel = registerLevel;
context.isPluginRegisteredInWorkspace = registerLevel === IPublicEnumPluginRegisterLevel.Workspace;
+ editor.set('pluginContext', context);
},
};