roymondchen aab73249d1 feat(editor): 新增 alwaysMultiSelect 配置开启常驻多选模式
新增编辑器配置项 alwaysMultiSelect(默认 false),开启后无需按住 Ctrl/Meta
键,组件树与画布点击即多选;当 disabledMultiSelect=true 时本配置失效。同步
在 stage 层 ActionManager 暴露 setAlwaysMultiSelect 方法用于运行时切换,并
补充组件树/服务/画布的状态联动、文档与单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 16:50:40 +08:00

256 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<Framework
:disabled-page-fragment="disabledPageFragment"
:page-bar-sort-options="pageBarSortOptions"
:page-filter-function="pageFilterFunction"
>
<template #header>
<slot name="header"></slot>
</template>
<template #nav>
<slot name="nav" :editorService="editorService"><TMagicNavMenu :data="menu"></TMagicNavMenu></slot>
</template>
<template #content-before>
<slot name="content-before"></slot>
</template>
<template #src-code><slot name="src-code" :editorService="editorService"></slot></template>
<template #sidebar>
<slot name="sidebar" :editorService="editorService">
<Sidebar
:data="sidebar"
:layer-content-menu="layerContentMenu"
:custom-content-menu="customContentMenu"
:indent="treeIndent"
:next-level-indent-increment="treeNextLevelIndentIncrement"
:layer-node-is-expandable="layerNodeIsExpandable"
:can-drop-in="canDropIn"
:before-layer-node-dblclick="beforeLayerNodeDblclick"
@layer-node-dblclick="layerNodeDblclickHandler"
>
<template #layer-panel-header>
<slot name="layer-panel-header"></slot>
</template>
<template #layer-node-content="{ data }">
<slot name="layer-node-content" :data="data"></slot>
</template>
<template #layer-node-label="{ data }">
<slot name="layer-node-label" :data="data"></slot>
</template>
<template #layer-node-tool="{ data }">
<slot name="layer-node-tool" :data="data"></slot>
</template>
<template #component-list="{ componentGroupList }">
<slot name="component-list" :component-group-list="componentGroupList"></slot>
</template>
<template #component-list-panel-header>
<slot name="component-list-panel-header"></slot>
</template>
<template #component-list-item="{ component }">
<slot name="component-list-item" :component="component"></slot>
</template>
<template #code-block-panel-header>
<slot name="code-block-panel-header"></slot>
</template>
<template #code-block-panel-tool="{ id, data }">
<slot name="code-block-panel-tool" :id="id" :data="data"></slot>
</template>
<template #code-block-panel-search>
<slot name="code-block-panel-search"></slot>
</template>
<template #data-source-panel-tool="{ data }">
<slot name="data-source-panel-tool" :data="data"></slot>
</template>
<template #data-source-panel-search>
<slot name="data-source-panel-search"></slot>
</template>
</Sidebar>
</slot>
</template>
<template #workspace>
<slot name="workspace" :editorService="editorService">
<Workspace
:disabled-stage-overlay="disabledStageOverlay"
:stage-content-menu="stageContentMenu"
:custom-content-menu="customContentMenu"
>
<template #stage-top><slot name="stage-top"></slot></template>
<template #stage><slot name="stage"></slot></template>
<template #workspace-content><slot name="workspace-content" :editorService="editorService"></slot></template>
</Workspace>
</slot>
</template>
<template #props-panel>
<slot name="props-panel">
<PropsPanel
:extend-state="extendFormState"
:disabled-show-src="disabledShowSrc"
@mounted="propsPanelMountedHandler"
@unmounted="propsPanelUnmountedHandler"
@form-error="propsPanelFormErrorHandler"
@submit-error="propsPanelSubmitErrorHandler"
>
<template #props-panel-header>
<slot name="props-panel-header"></slot>
</template>
</PropsPanel>
</slot>
</template>
<template #empty><slot name="empty" :editorService="editorService"></slot></template>
<template #content-after>
<slot name="content-after"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
<template #page-bar><slot name="page-bar"></slot></template>
<template #page-bar-add-button><slot name="page-bar-add-button"></slot></template>
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
<template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
</Framework>
</template>
<script lang="ts" setup>
import { EventEmitter } from 'events';
import { provide } from 'vue';
import type { MApp } from '@tmagic/core';
import Framework from './layouts/Framework.vue';
import TMagicNavMenu from './layouts/NavMenu.vue';
import FormPanel from './layouts/props-panel/FormPanel.vue';
import PropsPanel from './layouts/props-panel/PropsPanel.vue';
import Sidebar from './layouts/sidebar/Sidebar.vue';
import Workspace from './layouts/workspace/Workspace.vue';
import codeBlockService from './services/codeBlock';
import componentListService from './services/componentList';
import dataSourceService from './services/dataSource';
import depService from './services/dep';
import editorService from './services/editor';
import eventsService from './services/events';
import historyService from './services/history';
import keybindingService from './services/keybinding';
import propsService from './services/props';
import stageOverlayService from './services/stageOverlay';
import storageService from './services/storage';
import uiService from './services/ui';
import keybindingConfig from './utils/keybinding-config';
import { defaultEditorProps, EditorProps } from './editorProps';
import { initServiceEvents, initServiceState } from './initService';
import type { TreeNodeData } from './type';
import type { EditorSlots, EventBus, Services, StageOptions } from './type';
defineSlots<EditorSlots>();
defineOptions({
name: 'MEditor',
});
const emit = defineEmits<{
'props-panel-mounted': [instance: InstanceType<typeof FormPanel>];
'props-panel-unmounted': [];
'update:modelValue': [value: MApp | null];
'props-form-error': [e: any];
'props-submit-error': [e: any];
'layer-node-dblclick': [event: MouseEvent, data: TreeNodeData];
}>();
const props = withDefaults(defineProps<EditorProps>(), defaultEditorProps);
const services: Services = {
componentListService,
eventsService,
historyService,
propsService,
editorService,
uiService,
storageService,
codeBlockService,
depService,
dataSourceService,
keybindingService,
stageOverlayService,
};
initServiceEvents(props, emit, services);
initServiceState(props, services);
keybindingService.register(keybindingConfig);
keybindingService.registerEl('global');
const stageOptions: StageOptions = {
runtimeUrl: props.runtimeUrl,
autoScrollIntoView: props.autoScrollIntoView,
render: props.render,
moveableOptions: props.moveableOptions,
canSelect: props.canSelect,
updateDragEl: props.updateDragEl,
isContainer: props.isContainer,
// sourceIds 为空表示从组件列表新增(尚无 id否则是画布上拖动已有组件
canDropIn: props.canDropIn
? (sourceIds, targetId) =>
props.canDropIn!(sourceIds, targetId, sourceIds.length === 0 ? 'stage-add' : 'stage-drag')
: undefined,
containerHighlightClassName: props.containerHighlightClassName,
containerHighlightDuration: props.containerHighlightDuration,
containerHighlightType: props.containerHighlightType,
disabledDragStart: props.disabledDragStart,
renderType: props.renderType,
guidesOptions: props.guidesOptions,
disabledMultiSelect: props.disabledMultiSelect,
alwaysMultiSelect: props.alwaysMultiSelect,
beforeDblclick: props.beforeDblclick,
};
stageOverlayService.set('stageOptions', stageOptions);
provide('services', services);
provide('codeOptions', props.codeOptions);
provide('stageOptions', stageOptions);
provide<EventBus>('eventBus', new EventEmitter());
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
emit('props-panel-mounted', e);
};
const propsPanelUnmountedHandler = () => {
emit('props-panel-unmounted');
};
const propsPanelSubmitErrorHandler = (e: any) => {
emit('props-submit-error', e);
};
const propsPanelFormErrorHandler = (e: any) => {
emit('props-form-error', e);
};
const layerNodeDblclickHandler = (event: MouseEvent, data: TreeNodeData) => {
emit('layer-node-dblclick', event, data);
};
defineExpose(services);
</script>