mirror of
				https://github.com/Tencent/tmagic-editor.git
				synced 2025-11-04 02:28:04 +08:00 
			
		
		
		
	feat(editor): 使用 floatbox 替换原抽屉栏
This commit is contained in:
		
							parent
							
								
									260286f9cf
								
							
						
					
					
						commit
						a035f02f83
					
				@ -1,6 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <component
 | 
			
		||||
    :is="slideType === 'box' ? MFormBox : MFormDrawer"
 | 
			
		||||
  <MFormBox
 | 
			
		||||
    class="m-editor-code-block-editor"
 | 
			
		||||
    ref="fomDrawer"
 | 
			
		||||
    label-width="80px"
 | 
			
		||||
@ -20,7 +19,7 @@
 | 
			
		||||
    <template #left>
 | 
			
		||||
      <TMagicButton type="primary" link @click="difVisible = true">查看修改</TMagicButton>
 | 
			
		||||
    </template>
 | 
			
		||||
  </component>
 | 
			
		||||
  </MFormBox>
 | 
			
		||||
 | 
			
		||||
  <TMagicDialog v-model="difVisible" title="查看修改" fullscreen>
 | 
			
		||||
    <div style="display: flex; margin-bottom: 10px">
 | 
			
		||||
@ -55,7 +54,7 @@ import { ColumnConfig, FormConfig, FormState, MFormBox, MFormDrawer } from '@tma
 | 
			
		||||
import type { CodeBlockContent } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
import CodeEditor from '@editor/layouts/CodeEditor.vue';
 | 
			
		||||
import type { Services, SlideType } from '@editor/type';
 | 
			
		||||
import type { Services } from '@editor/type';
 | 
			
		||||
import { getConfig } from '@editor/utils/config';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
@ -67,7 +66,6 @@ const props = defineProps<{
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  isDataSource?: boolean;
 | 
			
		||||
  dataSourceType?: string;
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <Teleport to="body" v-if="visible">
 | 
			
		||||
    <div ref="target" class="m-editor-float-box" :style="style">
 | 
			
		||||
    <div ref="target" class="m-editor-float-box" :style="style" @mousedown="nextZIndex">
 | 
			
		||||
      <div ref="dragTarget" class="m-editor-float-box-title">
 | 
			
		||||
        <slot name="title">
 | 
			
		||||
          <span>{{ title }}</span>
 | 
			
		||||
@ -21,7 +21,7 @@ import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
 | 
			
		||||
import { Close } from '@element-plus/icons-vue';
 | 
			
		||||
import VanillaMoveable from 'moveable';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton } from '@tmagic/design';
 | 
			
		||||
import { TMagicButton, useZIndex } from '@tmagic/design';
 | 
			
		||||
 | 
			
		||||
interface Position {
 | 
			
		||||
  left: number;
 | 
			
		||||
@ -47,11 +47,15 @@ const emit = defineEmits<{
 | 
			
		||||
const target = ref<HTMLDivElement>();
 | 
			
		||||
const dragTarget = ref<HTMLDivElement>();
 | 
			
		||||
 | 
			
		||||
const zIndex = useZIndex();
 | 
			
		||||
const curZIndex = ref<number>(zIndex.nextZIndex());
 | 
			
		||||
 | 
			
		||||
const style = computed(() => ({
 | 
			
		||||
  left: `${props.position.left}px`,
 | 
			
		||||
  top: `${props.position.top}px`,
 | 
			
		||||
  width: typeof props.rect.width === 'string' ? props.rect.width : `${props.rect.width}px`,
 | 
			
		||||
  height: typeof props.rect.height === 'string' ? props.rect.height : `${props.rect.height}px`,
 | 
			
		||||
  zIndex: curZIndex.value,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
let moveable: VanillaMoveable | null = null;
 | 
			
		||||
@ -70,6 +74,7 @@ const initMoveable = () => {
 | 
			
		||||
    dragTargetSelf: false,
 | 
			
		||||
    linePadding: 10,
 | 
			
		||||
    controlPadding: 10,
 | 
			
		||||
    bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  moveable.on('drag', (e) => {
 | 
			
		||||
@ -111,6 +116,10 @@ const closeHandler = () => {
 | 
			
		||||
  emit('update:visible', false);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const nextZIndex = () => {
 | 
			
		||||
  curZIndex.value = zIndex.nextZIndex();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  target,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,6 @@ export const useCodeBlockEdit = (codeBlockService?: CodeBlockService) => {
 | 
			
		||||
    codeId.value = id;
 | 
			
		||||
 | 
			
		||||
    await nextTick();
 | 
			
		||||
 | 
			
		||||
    codeBlockEditor.value?.show();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="m-editor-nav-menu" :style="{ height: `${height}px` }">
 | 
			
		||||
  <div class="m-editor-nav-menu" :style="{ height: `${height}px` }" ref="navMenu">
 | 
			
		||||
    <div v-for="key in keys" :class="`menu-${key}`" :key="key" :style="`width: ${columnWidth?.[key]}px`">
 | 
			
		||||
      <ToolButton :data="item" v-for="(item, index) in buttons[key]" :key="index"></ToolButton>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, inject, markRaw } from 'vue';
 | 
			
		||||
import { computed, inject, markRaw, onBeforeUnmount, onMounted, ref } from 'vue';
 | 
			
		||||
import { Back, Delete, FullScreen, Grid, Memo, Right, ScaleToOriginal, ZoomIn, ZoomOut } from '@element-plus/icons-vue';
 | 
			
		||||
 | 
			
		||||
import { NodeType } from '@tmagic/schema';
 | 
			
		||||
@ -178,4 +178,23 @@ const buttons = computed(() => {
 | 
			
		||||
  });
 | 
			
		||||
  return data;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const resizeObserver = new ResizeObserver(() => {
 | 
			
		||||
  const rect = navMenu.value?.getBoundingClientRect();
 | 
			
		||||
  if (rect) {
 | 
			
		||||
    uiService?.set('navMenuRect', {
 | 
			
		||||
      left: rect.left,
 | 
			
		||||
      top: rect.top,
 | 
			
		||||
      width: rect.width,
 | 
			
		||||
      height: rect.height,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
const navMenu = ref<HTMLDivElement>();
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  navMenu.value && resizeObserver.observe(navMenu.value);
 | 
			
		||||
});
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  resizeObserver.disconnect();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -204,6 +204,11 @@ const getItemConfig = (data: SideItem): SideComponent => {
 | 
			
		||||
      text: '代码编辑',
 | 
			
		||||
      component: CodeBlockListPanel,
 | 
			
		||||
      slots: {},
 | 
			
		||||
      boxComponentConfig: {
 | 
			
		||||
        props: {
 | 
			
		||||
          slideType: 'box',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    'data-source': {
 | 
			
		||||
      $key: 'data-source',
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <slot name="code-block-panel-header">
 | 
			
		||||
      <div class="search-wrapper">
 | 
			
		||||
        <SearchInput @search="filterTextChangeHandler"></SearchInput>
 | 
			
		||||
        <TMagicButton v-if="editable" class="create-code-button" type="primary" size="small" @click="createCodeBlock"
 | 
			
		||||
        <TMagicButton v-if="editable" class="create-code-button" type="primary" size="small" @click="showCreate"
 | 
			
		||||
          >新增</TMagicButton
 | 
			
		||||
        >
 | 
			
		||||
        <slot name="code-block-panel-search"></slot>
 | 
			
		||||
@ -11,7 +11,7 @@
 | 
			
		||||
    </slot>
 | 
			
		||||
 | 
			
		||||
    <!-- 代码块列表 -->
 | 
			
		||||
    <CodeBlockList ref="codeBlockList" :custom-error="customError" @edit="editCode" @remove="deleteCode">
 | 
			
		||||
    <CodeBlockList ref="codeBlockList" :custom-error="customError" @edit="showEdit" @remove="deleteCode">
 | 
			
		||||
      <template #code-block-panel-tool="{ id, data }">
 | 
			
		||||
        <slot name="code-block-panel-tool" :id="id" :data="data"></slot>
 | 
			
		||||
      </template>
 | 
			
		||||
@ -19,23 +19,31 @@
 | 
			
		||||
  </TMagicScrollbar>
 | 
			
		||||
 | 
			
		||||
  <!-- 代码块编辑区 -->
 | 
			
		||||
  <FloatingBox v-model:visible="popVisible" title="代码编辑" :position="boxPosition">
 | 
			
		||||
    <template #body>
 | 
			
		||||
      <div ref="scrollBar"></div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </FloatingBox>
 | 
			
		||||
 | 
			
		||||
  <Teleport :to="scrollBar" :disabled="slideType === 'box'" v-if="editVisible">
 | 
			
		||||
    <CodeBlockEditor
 | 
			
		||||
      v-if="codeConfig"
 | 
			
		||||
      ref="codeBlockEditor"
 | 
			
		||||
      :disabled="!editable"
 | 
			
		||||
      :content="codeConfig"
 | 
			
		||||
    :slideType="slideType"
 | 
			
		||||
      @submit="submitCodeBlockHandler"
 | 
			
		||||
    ></CodeBlockEditor>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, inject, ref } from 'vue';
 | 
			
		||||
import { computed, inject, nextTick, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
 | 
			
		||||
import type { Id } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue';
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import SearchInput from '@editor/components/SearchInput.vue';
 | 
			
		||||
import { useCodeBlockEdit } from '@editor/hooks/use-code-block-edit';
 | 
			
		||||
import type { CodeBlockListPanelSlots, CodeDeleteErrorType, Services, SlideType } from '@editor/type';
 | 
			
		||||
@ -48,12 +56,12 @@ defineOptions({
 | 
			
		||||
  name: 'MEditorCodeBlockListPanel',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineProps<{
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const { codeBlockService } = inject<Services>('services') || {};
 | 
			
		||||
const { codeBlockService, uiService } = inject<Services>('services') || {};
 | 
			
		||||
 | 
			
		||||
const editable = computed(() => codeBlockService?.getEditStatus());
 | 
			
		||||
 | 
			
		||||
@ -65,4 +73,35 @@ const codeBlockList = ref<InstanceType<typeof CodeBlockList>>();
 | 
			
		||||
const filterTextChangeHandler = (val: string) => {
 | 
			
		||||
  codeBlockList.value?.filter(val);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const boxPosition = computed(() => {
 | 
			
		||||
  const columnWidth = uiService?.get('columnWidth');
 | 
			
		||||
  const navMenuRect = uiService?.get('navMenuRect');
 | 
			
		||||
  return {
 | 
			
		||||
    left: columnWidth?.left ?? 0,
 | 
			
		||||
    top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const scrollBar = ref<HTMLDivElement>();
 | 
			
		||||
const popVisible = ref<boolean>(false);
 | 
			
		||||
const editVisible = ref<boolean>(false);
 | 
			
		||||
 | 
			
		||||
const beforeShowEdit = async () => {
 | 
			
		||||
  if (props.slideType !== 'box') {
 | 
			
		||||
    popVisible.value = true;
 | 
			
		||||
  }
 | 
			
		||||
  await nextTick();
 | 
			
		||||
  editVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showEdit = async (id: string) => {
 | 
			
		||||
  await beforeShowEdit();
 | 
			
		||||
  editCode(id);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showCreate = async () => {
 | 
			
		||||
  await beforeShowEdit();
 | 
			
		||||
  createCodeBlock();
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -47,8 +47,13 @@ const state = reactive<UiState>({
 | 
			
		||||
  showRule: true,
 | 
			
		||||
  propsPanelSize: 'small',
 | 
			
		||||
  showAddPageButton: true,
 | 
			
		||||
  floatBox: new Map(),
 | 
			
		||||
  hideSlideBar: false,
 | 
			
		||||
  navMenuRect: {
 | 
			
		||||
    left: 0,
 | 
			
		||||
    top: 0,
 | 
			
		||||
    width: 0,
 | 
			
		||||
    height: 0,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const canUsePluginMethods = {
 | 
			
		||||
@ -110,19 +115,6 @@ class Ui extends BaseService {
 | 
			
		||||
    return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async setFloatBox(keys: string[]) {
 | 
			
		||||
    const map = state.floatBox;
 | 
			
		||||
    for (const key of keys) {
 | 
			
		||||
      if (map.get(key)) continue;
 | 
			
		||||
      map.set(key, {
 | 
			
		||||
        status: false,
 | 
			
		||||
        zIndex: 99,
 | 
			
		||||
        top: 0,
 | 
			
		||||
        left: 0,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public resetState() {
 | 
			
		||||
    this.set('showSrc', false);
 | 
			
		||||
    this.set('uiSelectMode', false);
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,9 @@
 | 
			
		||||
.m-container-vs-code {
 | 
			
		||||
  .el-form-item {
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.magic-code-editor {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -72,12 +72,15 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.m-editor-slide-list-box {
 | 
			
		||||
  min-width: 270px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  min-width: 240px;
 | 
			
		||||
  min-height: 500px;
 | 
			
		||||
  max-height: 1024px;
 | 
			
		||||
  > div {
 | 
			
		||||
    &:first-child {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      min-width: 240px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .m-form-box {
 | 
			
		||||
    border-left: 1px solid #e0e0e0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -236,20 +236,16 @@ export interface UiState {
 | 
			
		||||
  propsPanelSize: 'large' | 'default' | 'small';
 | 
			
		||||
  /** 是否显示新增页面按钮 */
 | 
			
		||||
  showAddPageButton: boolean;
 | 
			
		||||
 | 
			
		||||
  /** slide 拖拽悬浮窗 state */
 | 
			
		||||
  floatBox: Map<
 | 
			
		||||
    string,
 | 
			
		||||
    {
 | 
			
		||||
      status: boolean;
 | 
			
		||||
      zIndex: number;
 | 
			
		||||
      top: number;
 | 
			
		||||
      left: number;
 | 
			
		||||
    }
 | 
			
		||||
  >;
 | 
			
		||||
 | 
			
		||||
  /** 是否隐藏侧边栏 */
 | 
			
		||||
  hideSlideBar: boolean;
 | 
			
		||||
 | 
			
		||||
  // navMenu 的宽高
 | 
			
		||||
  navMenuRect: {
 | 
			
		||||
    left: number;
 | 
			
		||||
    top: number;
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface EditorNodeInfo {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  padding: 16px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 90%;
 | 
			
		||||
  .el-box__header {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
 | 
			
		||||
import React, { constructor, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
 | 
			
		||||
import type { MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
 | 
			
		||||
@ -16,8 +15,8 @@ const PageFragmentContainer: React.FC<PageFragmentContainerProps> = ({ config })
 | 
			
		||||
 | 
			
		||||
  if (!app) return null;
 | 
			
		||||
  const MagicUiContainer = app.resolveComponent('container');
 | 
			
		||||
  let containerConfig = {}
 | 
			
		||||
  const fragment = app?.dsl?.items?.find((page) => page.id === config.pageFragmentId)
 | 
			
		||||
  let containerConfig = {};
 | 
			
		||||
  const fragment = app?.dsl?.items?.find((page) => page.id === config.pageFragmentId);
 | 
			
		||||
  if (fragment) {
 | 
			
		||||
    const { id, type, items, ...others } = fragment;
 | 
			
		||||
    const itemsWithoutId = items.map((item: MNode) => {
 | 
			
		||||
@ -32,8 +31,8 @@ const PageFragmentContainer: React.FC<PageFragmentContainerProps> = ({ config })
 | 
			
		||||
    } else {
 | 
			
		||||
      containerConfig = {
 | 
			
		||||
        ...others,
 | 
			
		||||
        items
 | 
			
		||||
      }
 | 
			
		||||
        items,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -43,9 +42,7 @@ const PageFragmentContainer: React.FC<PageFragmentContainerProps> = ({ config })
 | 
			
		||||
      className="magic-ui-page-fragment-container"
 | 
			
		||||
      style={app.transformStyle(config.style || {})}
 | 
			
		||||
    >
 | 
			
		||||
      <MagicUiContainer
 | 
			
		||||
        config={containerConfig}
 | 
			
		||||
      ></MagicUiContainer>
 | 
			
		||||
      <MagicUiContainer config={containerConfig}></MagicUiContainer>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import type { MComponent, MContainer, MPageFragment } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user