注意:写文章的时候,我用的xflow版本是1.x的
1. 使用
<KeyBindings config={keyBindingConfigs()} />
2.配置
// keyBindingConfigs
import { DeleteOutlined, EditOutlined, StopOutlined, UngroupOutlined } from '@ant-design/icons';
import {
createCtxMenuConfig,
IconStore,
IMenuOptions,
MenuItemType,
NsGraph,
} from '@antv/xflow';
import RenameNodeCmd, { RenameNodeCommandArgs } from '../commands/rename';
/** menuitem 配置 */
/** 注册菜单依赖的icon */
IconStore.set('DeleteOutlined', DeleteOutlined);
IconStore.set('EditOutlined', EditOutlined);
IconStore.set('StopOutlined', StopOutlined);
IconStore.set('UngroupOutlined', UngroupOutlined);
// 具体配置参考文档
export const MenuItemConfig: Record<string, IMenuOptions> = {
RENAME_NODE: {
id: RenameNodeCmd.command.id,
label: '重命名',
iconName: 'EditOutlined',
// 点击菜单项后触发的逻辑
onClick: async ({ target, commandService }) => {
const nodeConfig = target.data as NsGraph.INodeConfig & { extra: NodeExtraData };
// 一般都是触发某个命令
commandService.executeCommand<RenameNodeCommandArgs>(RenameNodeCmd.command.id, {
nodeConfig,
// 表单参数
formConfig: {
name: 'name',
label: '名称',
},
});
},
},
};
export default createCtxMenuConfig((config, proxy) => {
config.setMenuModelService(async (data, model, modelService, toDispose) => {
const { type, cell } = data!;
// 根据触发菜单的目标不同可以设定不同的菜单内容
switch (type) {
/** 节点菜单 */
case 'node': {
const cellData = cell.getData<NsGraph.INodeConfig>();
const submenu = cellData.isGroup
? [MenuItemConfig.DEL_GROUP, MenuItemConfig.RENAME_NODE]
: [MenuItemConfig.DELETE_NODE, MenuItemConfig.RENAME_NODE];
model.setValue({
id: 'root',
type: MenuItemType.Root,
submenu: submenu,
});
break;
}
/** 边菜单 */
case 'edge':
model.setValue({
id: 'root',
type: MenuItemType.Root,
submenu: [MenuItemConfig.DELETE_EDGE],
});
break;
/** 画布菜单 */
case 'blank':
model.setValue({
id: 'root',
type: MenuItemType.Root,
submenu: [MenuItemConfig.EMPTY_MENU],
});
break;
/** 默认菜单 */
default:
model.setValue({
id: 'root',
type: MenuItemType.Root,
submenu: [MenuItemConfig.READ_ONLY],
});
break;
}
});
});
3. 弹窗表单
上面用到了一个自定义命令RenameNodeCmd
,这个命令的效果如下:
当然这个思路不仅仅可以做弹窗,其他功能也能结合。代码参考的xflow
,代码地址
3.1 思路
- 点击重命名,触发命令,然后弹出弹窗表单
- 表单点击确定,关闭弹窗把数据返回,然后修改节点数据
3.2 新建命令
这里解释一下:
在我的业务中,我把后端传来的信息全部存在了node.extra
中,也就是我自己定义的一个字段,所以显示的数据和修改的数据都是这个字段中的。
所以你们可以根据自己的需要修改。
import type {
HookHub,
ICmdHooks as IHooks,
ICommandContributionConfig,
NsGraph,
} from '@antv/xflow';
import { ManaSyringe } from '@antv/xflow';
import type { IArgsBase, ICommandHandler } from '@antv/xflow';
import { ICommandContextProvider } from '@antv/xflow';
// 自定义参数
export interface RenameNodeCommandArgs extends IArgsBase {
nodeConfig: NsGraph.INodeConfig & { extra?: NodeExtraData };
/**
* 表单字段配置
*
* 这里其实可以改成JSONFrom,那就更方便了
*/
formConfig: {
name: string;
label: string;
};
}
// 这个Result在这里面没有用到
// 实际上给hook用的
// 下面都是hook的配置,可以忽略,没用到
export interface RenameNodeCommandResult {
err: string | null;
preNodeName?: string;
currentNodeName?: string;
}
export interface RenameNodeCommandHooks extends IHooks {
renameNode: HookHub<RenameNodeCommandArgs, RenameNodeCommandResult>;
}
type ICommand = ICommandHandler<
RenameNodeCommandArgs,
RenameNodeCommandResult,
RenameNodeCommandHooks
>;
@ManaSyringe.injectable()
/** 部署画布数据 */
export class RenameNodeCommand implements ICommand {
@ManaSyringe.inject(ICommandContextProvider) contextProvider: ICommand['contextProvider'];
/** 执行Cmd */
execute = async () => {
const ctx = this.contextProvider();
const { args } = ctx.getArgs();
const hooks = ctx.getHooks();
// 执行命令,因为要触发hook所以就是hooks.xxx
const result = await hooks.renameNode.call(args, async (args) => {
const { nodeConfig, formConfig } = args;
// 保存修改前的数据 nodeConfig.extra?.name是我的业务数据
// 你可以根据需要自己修改
const preNodeName = nodeConfig.extra?.name;
const x6Graph = await ctx.getX6Graph();
// 获取操作的节点
const cell = x6Graph.getCellById(nodeConfig.id);
if (!cell || !cell.isNode()) {
return { err: `${nodeConfig.id}不是合法节点`, preNodeName, currentNodeName: '' };
}
// 获取表单数据
const formValues = await showModal(nodeConfig, args);
// 设置node数据
return { err: null, preNodeName, currentNodeName: '' };
});
ctx.setResult(result ?? { err: null });
return this;
};
/** undo cmd */
undo = async () => {
if (this.isUndoable()) {
const ctx = this.contextProvider();
ctx.undo();
}
return this;
};
/** redo cmd */
redo = async () => {
if (!this.isUndoable()) {
await this.execute();
}
return this;
};
isUndoable(): boolean {
const ctx = this.contextProvider();
return ctx.isUndoable();
}
}
const RenameNodeCmd: ICommandContributionConfig = {
/** Command: 用于注册named factory */
command: {
id: 'xflow:rename-node',
label: '重命名节点',
category: '节点操作',
},
/** hook name */
hookKey: 'renameNode',
CommandHandler: RenameNodeCommand,
};
export default RenameNodeCmd;
3.3 showModal
实现
弹窗有两个功能:
- 收集表单信息
- 返回表单数据
弹窗我没有使用antd-pro
的ModalForm
因为无法编程触发,所以我用的antd
的modal
加ProForm
function showModal(node: NsGraph.INodeConfig, commandArgs: RenameNodeCommandArgs) {
/** showModal 返回一个Promise */
// 这个Deferred是xflow自带的
// 作用你就想象成更方便一点的return Promise
const defer = new Deferred<Record<any, any>>();
alertModal({
content: (modal) => (
<ProForm
onFinish={async (values) => {
// 提交的时候返回数据
defer.resolve(values);
modal.destroy();
}}
initialValues={{ ...(node.extra ?? node) }}
>
<ProFormText
label={commandArgs.formConfig.label}
name={commandArgs.formConfig.name}
formItemProps={{ rules: [{ required: true }] }}
/>
</ProForm>
),
closable: true,
onCancel: defer.resolve,
footer: null,
width: 800,
});
return defer.promise;
}
上面的alertModal
就是触发一个Modal
,封装了一下,主要是为了能在表单中关闭弹窗。
export const alertModal = ({ content, ...modalPorps }: AlertModalProps) => {
const modal = Modal.confirm({
...modalPorps,
icon: null,
});
// 把modal实例又传给了内容组件
// 这样内容组件就可以关闭弹窗了
modal.update({
content: content(modal),
});
};
3.4. 修改节点数据
获取到表单数据后就可以修改节点数据了
// ....
// 获取表单数据
const formValues = await showModal(nodeConfig, args);
// formConfig就是指定了表单name和label叫什么
if (formValues && formValues[formConfig.name]) {
// 获取节点数据
const cellData = cell.getData<NsGraph.INodeConfig & { extra?: NodeExtraData }>();
// 如果是修改的分组名称
if (cellData.isGroup) {
// 主要就是用setData修改数据
cell.setData({
...cellData,
label: formValues[formConfig.name],
} as NsGraph.INodeConfig);
} else {
// 修改的节点名称
cell.setData({
...cellData,
// 这个extra是我自己定义的字段,根据情况自行定义
extra: { ...(cellData.extra ?? {}), [formConfig.name]: formValues[formConfig.name] },
} as NsGraph.INodeConfig);
}
return { err: null, preNodeName, currentNodeName: formValues[formConfig.name] };
}
// ....