mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-11-01 07:20:39 +00:00
feat(editor): 新增从组件面板点击/拖动添加组件前的处理函数配置
This commit is contained in:
parent
c4cf3b9525
commit
ccfc8c0733
@ -1590,6 +1590,74 @@ const onLayerNodeDblclick = (event, data) => {
|
||||
- 返回 `false` 时,会同时阻断默认的"展开/收起"行为以及向上抛出的 [`layer-node-dblclick`](./events.md#layer-node-dblclick) 事件;返回其他值则继续触发默认行为并抛出事件。
|
||||
:::
|
||||
|
||||
## beforeAppendComponent
|
||||
|
||||
- **详情:**
|
||||
|
||||
从组件列表新增组件前的钩子函数
|
||||
|
||||
在用户点击组件列表项时,先于写入节点执行。入参是当前组件列表项 `ComponentItem`(见 [`componentGroupList`](#componentgrouplist))。允许异步函数;返回 `false` 时取消本次新增。返回其他值(包括 `true`、`undefined`)则继续新增。
|
||||
|
||||
拖入画布不走该钩子,拖入请使用 [`beforeDropComponent`](#beforedropcomponent)。
|
||||
|
||||
可以直接修改入参(例如补全 `data`),修改会反映到最终写入的节点上。
|
||||
|
||||
- **默认值:** `undefined`
|
||||
|
||||
- **类型:** `(item: ComponentItem) => boolean | void | Promise<boolean | void>`
|
||||
|
||||
- **示例:**
|
||||
|
||||
```html
|
||||
<template>
|
||||
<m-editor :before-append-component="beforeAppendComponent"></m-editor>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const beforeAppendComponent = async (item) => {
|
||||
if (item.type === 'paid-button') {
|
||||
const allowed = await checkPermission(item.type);
|
||||
if (!allowed) return false;
|
||||
}
|
||||
item.data = { ...item.data, from: 'component-list' };
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
::: tip
|
||||
该钩子只作用于组件列表的点击新增,不影响拖入画布、复制、粘贴、页面新增等其它写入路径。
|
||||
:::
|
||||
|
||||
## beforeDropComponent
|
||||
|
||||
- **详情:**
|
||||
|
||||
组件列表拖入画布、落点前的钩子函数
|
||||
|
||||
仅当拖拽数据的 `dragType` 为组件列表(`component-list`)时,在位置计算完成、写入节点前执行。入参是即将新增的节点 `dragData` 和父节点 `parent`。允许异步函数;返回 `false` 时取消本次拖入。返回其他值(包括 `true`、`undefined`)则继续新增。
|
||||
|
||||
点击组件列表新增不会触发该钩子,点击请使用 [`beforeAppendComponent`](#beforeappendcomponent)。
|
||||
|
||||
- **默认值:** `undefined`
|
||||
|
||||
- **类型:** `(dragData: AddMNode, parent: MContainer) => boolean | void | Promise<boolean | void>`
|
||||
|
||||
- **示例:**
|
||||
|
||||
```html
|
||||
<template>
|
||||
<m-editor :before-drop-component="beforeDropComponent"></m-editor>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const beforeDropComponent = async (dragData, parent) => {
|
||||
if (parent.type === 'page' && dragData.type === 'paid-button') {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## 表单业务上下文
|
||||
|
||||
- **详情:**
|
||||
|
||||
@ -230,6 +230,8 @@ const stageOptions: StageOptions = {
|
||||
alwaysMultiSelect: props.alwaysMultiSelect,
|
||||
disabledFlashTip: props.disabledFlashTip,
|
||||
beforeDblclick: props.beforeDblclick,
|
||||
beforeAppendComponent: props.beforeAppendComponent,
|
||||
beforeDropComponent: props.beforeDropComponent,
|
||||
};
|
||||
|
||||
stageOverlayService.set('stageOptions', stageOptions);
|
||||
|
||||
@ -13,6 +13,8 @@ import StageCore, {
|
||||
import { getIdFromEl } from '@tmagic/utils';
|
||||
|
||||
import type {
|
||||
BeforeAppendComponent,
|
||||
BeforeDropComponent,
|
||||
CanDropInFunction,
|
||||
ComponentGroup,
|
||||
CustomContentMenuFunction,
|
||||
@ -160,6 +162,17 @@ export interface EditorProps {
|
||||
canDropIn?: CanDropInFunction;
|
||||
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
||||
/**
|
||||
* 点击组件列表新增组件前的钩子,入参为当前组件列表项,允许异步函数。
|
||||
* 仅点击新增时调用,拖入画布不走该钩子;返回 false 取消本次新增。
|
||||
* 可直接修改入参(例如补全 data),修改会反映到最终新增的节点上。
|
||||
*/
|
||||
beforeAppendComponent?: BeforeAppendComponent;
|
||||
/**
|
||||
* 组件列表拖入画布、写入节点前的钩子,入参为即将新增的节点和父节点,允许异步函数。
|
||||
* 仅当拖拽类型为组件列表时,在位置计算完成之后调用;返回 false 取消本次拖入。
|
||||
*/
|
||||
beforeDropComponent?: BeforeDropComponent;
|
||||
/** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeLayerNodeDblclick?: (event: MouseEvent, data: TreeNodeData) => Promise<boolean | void> | boolean | void;
|
||||
/** 历史记录面板的自定义扩展 tab,追加在内置的页面/数据源/代码块 tab 之后 */
|
||||
|
||||
@ -97,7 +97,13 @@ let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
let clientX: number;
|
||||
let clientY: number;
|
||||
|
||||
const appendComponent = ({ text, type, data = {} }: ComponentItem): void => {
|
||||
const appendComponent = async (item: ComponentItem): Promise<void> => {
|
||||
if (stageOptions?.beforeAppendComponent) {
|
||||
const result = await stageOptions.beforeAppendComponent(item);
|
||||
if (result === false) return;
|
||||
}
|
||||
|
||||
const { text, type, data = {} } = item;
|
||||
editorService.add(
|
||||
{
|
||||
name: text,
|
||||
|
||||
@ -450,6 +450,11 @@ const dropHandler = async (e: DragEvent) => {
|
||||
|
||||
dragData.inputEvent = e;
|
||||
|
||||
if (props.stageOptions.beforeDropComponent) {
|
||||
const result = await props.stageOptions.beforeDropComponent(dragData, parent);
|
||||
if (result === false) return;
|
||||
}
|
||||
|
||||
editorService.add(dragData, parent, { historySource: 'component-panel' });
|
||||
}
|
||||
};
|
||||
|
||||
@ -225,6 +225,16 @@ export interface StageOptions {
|
||||
iframeSandbox?: string | false;
|
||||
/** 画布双击前的钩子函数,返回 false 则阻止默认的双击行为 */
|
||||
beforeDblclick?: (event: MouseEvent) => Promise<boolean | void> | boolean | void;
|
||||
/**
|
||||
* 点击组件列表新增组件前的钩子,详见 EditorProps.beforeAppendComponent。
|
||||
* 仅点击新增时调用;返回 false 取消新增,支持异步。
|
||||
*/
|
||||
beforeAppendComponent?: BeforeAppendComponent;
|
||||
/**
|
||||
* 组件列表拖入画布、写入节点前的钩子,详见 EditorProps.beforeDropComponent。
|
||||
* 仅 dragType 为组件列表时调用;返回 false 取消本次拖入,支持异步。
|
||||
*/
|
||||
beforeDropComponent?: BeforeDropComponent;
|
||||
}
|
||||
|
||||
// #region NodeInvalidInfo
|
||||
@ -678,6 +688,18 @@ export interface ComponentItem {
|
||||
}
|
||||
// #endregion ComponentItem
|
||||
|
||||
/**
|
||||
* 点击组件列表新增组件前的钩子。
|
||||
* 入参为当前组件列表项,允许异步;返回 false 时取消本次新增。
|
||||
*/
|
||||
export type BeforeAppendComponent = (item: ComponentItem) => boolean | void | Promise<boolean | void>;
|
||||
|
||||
/**
|
||||
* 组件列表拖入画布、写入节点前的钩子。
|
||||
* 入参为即将新增的节点和父节点,允许异步;返回 false 时取消本次拖入。
|
||||
*/
|
||||
export type BeforeDropComponent = (dragData: AddMNode, parent: MContainer) => boolean | void | Promise<boolean | void>;
|
||||
|
||||
// #region ComponentGroup
|
||||
export interface ComponentGroup {
|
||||
/** 显示文案 */
|
||||
|
||||
@ -211,6 +211,24 @@ describe('Editor', () => {
|
||||
expect(canDropIn).toHaveBeenLastCalledWith(['s1'], 't1', 'stage-drag');
|
||||
});
|
||||
|
||||
test('beforeDropComponent 透传到 stageOptions', async () => {
|
||||
const beforeDropComponent = vi.fn();
|
||||
const stageOverlayMod = (await import('@editor/services/stageOverlay')) as any;
|
||||
mount(Editor, { props: { beforeDropComponent } as any });
|
||||
await nextTick();
|
||||
const stageOptions = stageOverlayMod.default.set.mock.calls.find((c: any[]) => c[0] === 'stageOptions')?.[1];
|
||||
expect(stageOptions.beforeDropComponent).toBe(beforeDropComponent);
|
||||
});
|
||||
|
||||
test('beforeAppendComponent 透传到 stageOptions', async () => {
|
||||
const beforeAppendComponent = vi.fn();
|
||||
const stageOverlayMod = (await import('@editor/services/stageOverlay')) as any;
|
||||
mount(Editor, { props: { beforeAppendComponent } as any });
|
||||
await nextTick();
|
||||
const stageOptions = stageOverlayMod.default.set.mock.calls.find((c: any[]) => c[0] === 'stageOptions')?.[1];
|
||||
expect(stageOptions.beforeAppendComponent).toBe(beforeAppendComponent);
|
||||
});
|
||||
|
||||
test('未传 canDropIn 时 stageOptions.canDropIn 为 undefined', async () => {
|
||||
const stageOverlayMod = (await import('@editor/services/stageOverlay')) as any;
|
||||
mount(Editor, { props: {} as any });
|
||||
|
||||
@ -8,6 +8,7 @@ import { defineComponent, h } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import ComponentListPanel from '@editor/layouts/sidebar/ComponentListPanel.vue';
|
||||
import type { ComponentItem } from '@editor/type';
|
||||
|
||||
const editorService = {
|
||||
get: vi.fn(),
|
||||
@ -137,4 +138,27 @@ describe('ComponentListPanel', () => {
|
||||
await item.trigger('drag', { clientX: 0, clientY: 0 });
|
||||
expect(stage.delayedMarkContainer).toHaveBeenCalledWith(expect.anything(), [], true);
|
||||
});
|
||||
|
||||
test('beforeAppendComponent 返回 false 时不新增', async () => {
|
||||
const beforeAppendComponent = vi.fn(async () => false);
|
||||
const wrapper = mount(ComponentListPanel, {
|
||||
global: { provide: { stageOptions: { beforeAppendComponent } } },
|
||||
});
|
||||
await wrapper.find('.component-item').trigger('click');
|
||||
expect(beforeAppendComponent).toHaveBeenCalledWith(expect.objectContaining({ text: '按钮', type: 'button' }));
|
||||
expect(editorService.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('beforeAppendComponent 异步修改组件后再新增', async () => {
|
||||
const beforeAppendComponent = vi.fn(async (item: ComponentItem) => {
|
||||
item.data = { text: 'hello' };
|
||||
});
|
||||
const wrapper = mount(ComponentListPanel, {
|
||||
global: { provide: { stageOptions: { beforeAppendComponent } } },
|
||||
});
|
||||
await wrapper.find('.component-item').trigger('click');
|
||||
expect(editorService.add).toHaveBeenCalledWith({ name: '按钮', type: 'button', text: 'hello' }, undefined, {
|
||||
historySource: 'component-panel',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -281,13 +281,50 @@ describe('Stage', () => {
|
||||
});
|
||||
|
||||
test('drop 非 COMPONENT_LIST 时不处理', async () => {
|
||||
const wrapper = mountIt();
|
||||
const beforeDropComponent = vi.fn();
|
||||
const wrapper = mountIt({
|
||||
stageOptions: { runtimeUrl: 'http://x', containerHighlightClassName: 'highlight', beforeDropComponent },
|
||||
});
|
||||
await nextTick();
|
||||
const event = createDropEvent('{"dragType":"other","data":{"name":"text","style":{}}}');
|
||||
const stageContainer = wrapper.find('.m-editor-stage-container').element;
|
||||
startInternalDrag();
|
||||
stageContainer.dispatchEvent(event);
|
||||
await waitMacrotask();
|
||||
expect(beforeDropComponent).not.toHaveBeenCalled();
|
||||
expect(editorService.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('drop beforeDropComponent 返回 false 时不新增', async () => {
|
||||
editorService.getNodeById.mockReturnValue(null);
|
||||
const beforeDropComponent = vi.fn(async () => false);
|
||||
const wrapper = mountIt({
|
||||
stageOptions: { runtimeUrl: 'http://x', containerHighlightClassName: 'highlight', beforeDropComponent },
|
||||
});
|
||||
await nextTick();
|
||||
const event = createDropEvent(COMPONENT_LIST_JSON);
|
||||
event.clientX = 10;
|
||||
event.clientY = 10;
|
||||
startInternalDrag();
|
||||
wrapper.find('.m-editor-stage-container').element.dispatchEvent(event);
|
||||
await waitMacrotask();
|
||||
expect(beforeDropComponent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: 'text', inputEvent: event }),
|
||||
expect.objectContaining({ id: 'p1' }),
|
||||
);
|
||||
expect(editorService.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('drop 缺少 data 时不调用 beforeDropComponent', async () => {
|
||||
const beforeDropComponent = vi.fn(async () => undefined);
|
||||
const wrapper = mountIt({
|
||||
stageOptions: { runtimeUrl: 'http://x', containerHighlightClassName: 'highlight', beforeDropComponent },
|
||||
});
|
||||
await nextTick();
|
||||
startInternalDrag();
|
||||
wrapper.find('.m-editor-stage-container').element.dispatchEvent(createDropEvent('{"dragType":"component-list"}'));
|
||||
await waitMacrotask();
|
||||
expect(beforeDropComponent).not.toHaveBeenCalled();
|
||||
expect(editorService.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user