roymondchen 8612311db1 feat(editor): 历史记录面板支持自定义扩展 tab 并开放 Bucket/goto 配置
新增 historyListExtraTabs 配置,可在内置页面/数据源/代码块 tab 后追加业务自定义历史 tab。
导出 HistoryListBucket 供复用,GroupRow 支持配置是否允许跳转,Bucket 支持配置是否展示初始项。
2026-06-01 19:21:36 +08:00

270 lines
9.3 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"
:hide-sidebar="hideSidebar"
>
<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);
/**
* 把顶层 `extendFormState` 提供给非 PropsPanel 链路上的组件使用(例如历史差异对话框 HistoryDiffDialog
* 内部的 CompareForm。这样所有依赖业务上下文的表单 filterFunction 都能拿到一致的扩展状态,
* 与 PropsPanel 通过 `:extend-state` 显式传入的方式保持等价。
*/
provide('extendFormState', props.extendFormState);
/**
* 把历史记录面板的自定义扩展 tab 提供给深层的 HistoryListPanel它挂在 NavMenu 中,
* 以 markRaw component 形式渲染,无法直接通过 props 透传)。业务方可借此在历史记录
* 面板内追加自定义模块的历史 tab。
*/
provide('historyListExtraTabs', props.historyListExtraTabs);
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>