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
							
								
									e2326ff4f6
								
							
						
					
					
						commit
						96149bd2ae
					
				@ -9,59 +9,57 @@
 | 
			
		||||
    :before-close="beforeClose"
 | 
			
		||||
  >
 | 
			
		||||
    <template #body>
 | 
			
		||||
      <div ref="floatingBoxBody" style="height: 100%"></div>
 | 
			
		||||
      <MFormBox
 | 
			
		||||
        class="m-editor-code-block-editor"
 | 
			
		||||
        ref="formBox"
 | 
			
		||||
        label-width="80px"
 | 
			
		||||
        :close-on-press-escape="false"
 | 
			
		||||
        :title="content.name"
 | 
			
		||||
        :config="functionConfig"
 | 
			
		||||
        :values="content"
 | 
			
		||||
        :disabled="disabled"
 | 
			
		||||
        style="height: 100%"
 | 
			
		||||
        @change="changeHandler"
 | 
			
		||||
        @submit="submitForm"
 | 
			
		||||
        @error="errorHandler"
 | 
			
		||||
        @closed="closedHandler"
 | 
			
		||||
      >
 | 
			
		||||
        <template #left>
 | 
			
		||||
          <TMagicButton v-if="!disabled" type="primary" link @click="difVisible = true">查看修改</TMagicButton>
 | 
			
		||||
        </template>
 | 
			
		||||
      </MFormBox>
 | 
			
		||||
    </template>
 | 
			
		||||
  </FloatingBox>
 | 
			
		||||
 | 
			
		||||
  <Teleport :to="floatingBoxBody" :disabled="slideType === 'box'" v-if="editVisible">
 | 
			
		||||
    <MFormBox
 | 
			
		||||
      class="m-editor-code-block-editor"
 | 
			
		||||
      ref="formBox"
 | 
			
		||||
      label-width="80px"
 | 
			
		||||
      :close-on-press-escape="false"
 | 
			
		||||
      :title="content.name"
 | 
			
		||||
      :config="functionConfig"
 | 
			
		||||
      :values="content"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      :height="floatingBoxBody?.clientHeight"
 | 
			
		||||
      @change="changeHandler"
 | 
			
		||||
      @submit="submitForm"
 | 
			
		||||
      @error="errorHandler"
 | 
			
		||||
      @closed="closedHandler"
 | 
			
		||||
    >
 | 
			
		||||
      <template #left>
 | 
			
		||||
        <TMagicButton v-if="!disabled" type="primary" link @click="difVisible = true">查看修改</TMagicButton>
 | 
			
		||||
  <Teleport to="body">
 | 
			
		||||
    <TMagicDialog title="查看修改" v-model="difVisible" fullscreen>
 | 
			
		||||
      <div style="display: flex; margin-bottom: 10px">
 | 
			
		||||
        <div style="flex: 1"><TMagicTag size="small" type="info">修改前</TMagicTag></div>
 | 
			
		||||
        <div style="flex: 1"><TMagicTag size="small" type="success">修改后</TMagicTag></div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <CodeEditor
 | 
			
		||||
        v-if="difVisible"
 | 
			
		||||
        ref="magicVsEditor"
 | 
			
		||||
        type="diff"
 | 
			
		||||
        language="json"
 | 
			
		||||
        :initValues="content.content"
 | 
			
		||||
        :modifiedValues="formBox?.form?.values.content"
 | 
			
		||||
        :style="`height: ${windowRect.height - 150}px`"
 | 
			
		||||
      ></CodeEditor>
 | 
			
		||||
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <span class="dialog-footer">
 | 
			
		||||
          <TMagicButton size="small" @click="difVisible = false">取消</TMagicButton>
 | 
			
		||||
          <TMagicButton size="small" type="primary" @click="diffChange">确定</TMagicButton>
 | 
			
		||||
        </span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </MFormBox>
 | 
			
		||||
    </TMagicDialog>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
 | 
			
		||||
  <TMagicDialog v-model="difVisible" title="查看修改" fullscreen>
 | 
			
		||||
    <div style="display: flex; margin-bottom: 10px">
 | 
			
		||||
      <div style="flex: 1"><TMagicTag size="small" type="info">修改前</TMagicTag></div>
 | 
			
		||||
      <div style="flex: 1"><TMagicTag size="small" type="success">修改后</TMagicTag></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <CodeEditor
 | 
			
		||||
      v-if="difVisible"
 | 
			
		||||
      ref="magicVsEditor"
 | 
			
		||||
      type="diff"
 | 
			
		||||
      language="json"
 | 
			
		||||
      :initValues="content.content"
 | 
			
		||||
      :modifiedValues="formBox?.form?.values.content"
 | 
			
		||||
      :style="`height: ${windowRect.height - 150}px`"
 | 
			
		||||
    ></CodeEditor>
 | 
			
		||||
 | 
			
		||||
    <template #footer>
 | 
			
		||||
      <span class="dialog-footer">
 | 
			
		||||
        <TMagicButton size="small" @click="difVisible = false">取消</TMagicButton>
 | 
			
		||||
        <TMagicButton size="small" type="primary" @click="diffChange">确定</TMagicButton>
 | 
			
		||||
      </span>
 | 
			
		||||
    </template>
 | 
			
		||||
  </TMagicDialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, inject, nextTick, ref } from 'vue';
 | 
			
		||||
import { computed, inject, Ref, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design';
 | 
			
		||||
import { ColumnConfig, FormConfig, FormState, MFormBox } from '@tmagic/form';
 | 
			
		||||
@ -69,9 +67,10 @@ import type { CodeBlockContent } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
 | 
			
		||||
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
 | 
			
		||||
import { useWindowRect } from '@editor/hooks/use-window-rect';
 | 
			
		||||
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({
 | 
			
		||||
@ -86,7 +85,6 @@ const props = defineProps<{
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  isDataSource?: boolean;
 | 
			
		||||
  dataSourceType?: string;
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
@ -229,7 +227,6 @@ const changeHandler = (values: CodeBlockContent) => {
 | 
			
		||||
 | 
			
		||||
const beforeClose = (done: (cancel?: boolean) => void) => {
 | 
			
		||||
  if (!changedValue.value) {
 | 
			
		||||
    editVisible.value = false;
 | 
			
		||||
    done();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@ -242,12 +239,10 @@ const beforeClose = (done: (cancel?: boolean) => void) => {
 | 
			
		||||
    })
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      changedValue.value && submitForm(changedValue.value);
 | 
			
		||||
      editVisible.value = false;
 | 
			
		||||
      done();
 | 
			
		||||
    })
 | 
			
		||||
    .catch((action: string) => {
 | 
			
		||||
      if (action === 'cancel') {
 | 
			
		||||
        editVisible.value = false;
 | 
			
		||||
      }
 | 
			
		||||
      done(action === 'cancel');
 | 
			
		||||
    });
 | 
			
		||||
@ -257,35 +252,17 @@ const closedHandler = () => {
 | 
			
		||||
  changedValue.value = undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const editVisible = ref<boolean>(false);
 | 
			
		||||
const floatingBoxBody = ref<HTMLDivElement>();
 | 
			
		||||
 | 
			
		||||
const boxPosition = computed(() => {
 | 
			
		||||
  const columnWidth = services?.uiService?.get('columnWidth');
 | 
			
		||||
  const navMenuRect = services?.uiService?.get('navMenuRect');
 | 
			
		||||
  return {
 | 
			
		||||
    left: columnWidth?.left ?? 0,
 | 
			
		||||
    top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
const parentFloating = inject<Ref<HTMLDivElement>>('parentFloating');
 | 
			
		||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(services?.uiService, parentFloating);
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  async show() {
 | 
			
		||||
    if (props.slideType !== 'box') {
 | 
			
		||||
      boxVisible.value = true;
 | 
			
		||||
      await nextTick();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    editVisible.value = true;
 | 
			
		||||
    calcBoxPosition();
 | 
			
		||||
    boxVisible.value = true;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async hide() {
 | 
			
		||||
    editVisible.value = false;
 | 
			
		||||
 | 
			
		||||
    if (props.slideType !== 'box') {
 | 
			
		||||
      await nextTick();
 | 
			
		||||
      boxVisible.value = false;
 | 
			
		||||
    }
 | 
			
		||||
    boxVisible.value = false;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -17,13 +17,14 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
 | 
			
		||||
import { computed, inject, nextTick, onBeforeUnmount, provide, ref, watch } from 'vue';
 | 
			
		||||
import { Close } from '@element-plus/icons-vue';
 | 
			
		||||
import VanillaMoveable from 'moveable';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton, useZIndex } from '@tmagic/design';
 | 
			
		||||
 | 
			
		||||
import MIcon from '@editor/components/Icon.vue';
 | 
			
		||||
import type { Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
interface Position {
 | 
			
		||||
  left: number;
 | 
			
		||||
@ -50,7 +51,7 @@ const target = ref<HTMLDivElement>();
 | 
			
		||||
const titleEl = ref<HTMLDivElement>();
 | 
			
		||||
 | 
			
		||||
const zIndex = useZIndex();
 | 
			
		||||
const curZIndex = ref<number>(zIndex.nextZIndex());
 | 
			
		||||
const curZIndex = ref<number>(0);
 | 
			
		||||
 | 
			
		||||
const titleHeight = ref(0);
 | 
			
		||||
const bodyHeight = computed(() => {
 | 
			
		||||
@ -65,12 +66,21 @@ const bodyHeight = computed(() => {
 | 
			
		||||
  return 'auto';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const style = computed(() => ({
 | 
			
		||||
  left: `${props.position.left}px`,
 | 
			
		||||
  top: `${props.position.top}px`,
 | 
			
		||||
  width: width.value ? `${width.value}px` : 'auto',
 | 
			
		||||
  height: height.value ? `${height.value}px` : 'auto',
 | 
			
		||||
}));
 | 
			
		||||
const services = inject<Services>('services');
 | 
			
		||||
const frameworkWidth = computed(() => services?.uiService.get('frameworkRect').width || 0);
 | 
			
		||||
const style = computed(() => {
 | 
			
		||||
  let { left } = props.position;
 | 
			
		||||
  if (width.value) {
 | 
			
		||||
    left = left + width.value > frameworkWidth.value ? frameworkWidth.value - width.value : left;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    left: `${left}px`,
 | 
			
		||||
    top: `${props.position.top}px`,
 | 
			
		||||
    width: width.value ? `${width.value}px` : 'auto',
 | 
			
		||||
    height: height.value ? `${height.value}px` : 'auto',
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let moveable: VanillaMoveable | null = null;
 | 
			
		||||
 | 
			
		||||
@ -114,6 +124,7 @@ watch(
 | 
			
		||||
  async (visible) => {
 | 
			
		||||
    if (visible) {
 | 
			
		||||
      await nextTick();
 | 
			
		||||
      curZIndex.value = zIndex.nextZIndex();
 | 
			
		||||
 | 
			
		||||
      const targetRect = target.value?.getBoundingClientRect();
 | 
			
		||||
      if (targetRect) {
 | 
			
		||||
@ -157,6 +168,8 @@ const nextZIndex = () => {
 | 
			
		||||
  curZIndex.value = zIndex.nextZIndex();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
provide('parentFloating', target);
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  bodyHeight,
 | 
			
		||||
  target,
 | 
			
		||||
 | 
			
		||||
@ -7,39 +7,57 @@
 | 
			
		||||
      <TMagicButton size="small" type="primary" :disabled="disabled" plain @click="newHandler()">添加</TMagicButton>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <MFormDrawer
 | 
			
		||||
      ref="addDialog"
 | 
			
		||||
      label-width="80px"
 | 
			
		||||
    <FloatingBox
 | 
			
		||||
      v-model:visible="addDialogVisible"
 | 
			
		||||
      v-model:width="width"
 | 
			
		||||
      v-model:height="editorHeight"
 | 
			
		||||
      :title="fieldTitle"
 | 
			
		||||
      :config="dataSourceFieldsConfig"
 | 
			
		||||
      :values="fieldValues"
 | 
			
		||||
      :parentValues="model[name]"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      @submit="fieldChange"
 | 
			
		||||
    ></MFormDrawer>
 | 
			
		||||
      :position="boxPosition"
 | 
			
		||||
    >
 | 
			
		||||
      <template #body>
 | 
			
		||||
        <MFormBox
 | 
			
		||||
          label-width="80px"
 | 
			
		||||
          :title="fieldTitle"
 | 
			
		||||
          :config="dataSourceFieldsConfig"
 | 
			
		||||
          :values="fieldValues"
 | 
			
		||||
          :parentValues="model[name]"
 | 
			
		||||
          :disabled="disabled"
 | 
			
		||||
          @submit="fieldChange"
 | 
			
		||||
        ></MFormBox>
 | 
			
		||||
      </template>
 | 
			
		||||
    </FloatingBox>
 | 
			
		||||
 | 
			
		||||
    <MFormDrawer
 | 
			
		||||
      ref="addFromJsonDialog"
 | 
			
		||||
    <FloatingBox
 | 
			
		||||
      v-model:visible="addFromJsonDialogVisible"
 | 
			
		||||
      v-model:width="width"
 | 
			
		||||
      v-model:height="editorHeight"
 | 
			
		||||
      title="快速添加数据定义"
 | 
			
		||||
      :config="jsonFromConfig"
 | 
			
		||||
      :values="jsonFromValues"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      @submit="addFromJsonFromChange"
 | 
			
		||||
    ></MFormDrawer>
 | 
			
		||||
      :position="boxPosition"
 | 
			
		||||
    >
 | 
			
		||||
      <template #body>
 | 
			
		||||
        <MFormBox
 | 
			
		||||
          :config="jsonFromConfig"
 | 
			
		||||
          :values="jsonFromValues"
 | 
			
		||||
          :disabled="disabled"
 | 
			
		||||
          @submit="addFromJsonFromChange"
 | 
			
		||||
        ></MFormBox>
 | 
			
		||||
      </template>
 | 
			
		||||
    </FloatingBox>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, inject, ref } from 'vue';
 | 
			
		||||
import { inject, Ref, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
 | 
			
		||||
import { type FieldProps, type FormConfig, type FormState, MFormDrawer } from '@tmagic/form';
 | 
			
		||||
import { type FieldProps, type FormConfig, type FormState, MFormBox } from '@tmagic/form';
 | 
			
		||||
import type { DataSchema } from '@tmagic/schema';
 | 
			
		||||
import { type ColumnConfig, MagicTable } from '@tmagic/table';
 | 
			
		||||
import { getDefaultValueFromFields } from '@tmagic/utils';
 | 
			
		||||
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import { useEditorContentHeight } from '@editor/hooks';
 | 
			
		||||
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
 | 
			
		||||
import type { Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
@ -61,17 +79,16 @@ const emit = defineEmits(['change']);
 | 
			
		||||
 | 
			
		||||
const services = inject<Services>('services');
 | 
			
		||||
 | 
			
		||||
const addDialog = ref<InstanceType<typeof MFormDrawer>>();
 | 
			
		||||
 | 
			
		||||
const fieldValues = ref<Record<string, any>>({});
 | 
			
		||||
const fieldTitle = ref('');
 | 
			
		||||
 | 
			
		||||
const width = computed(() => globalThis.document.body.clientWidth - (services?.uiService.get('columnWidth').left || 0));
 | 
			
		||||
const width = defineModel<number>('width', { default: 670 });
 | 
			
		||||
 | 
			
		||||
const newHandler = () => {
 | 
			
		||||
  fieldValues.value = {};
 | 
			
		||||
  fieldTitle.value = '新增属性';
 | 
			
		||||
  addDialog.value?.show();
 | 
			
		||||
  calcBoxPosition();
 | 
			
		||||
  addDialogVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fieldChange = ({ index, ...value }: Record<string, any>) => {
 | 
			
		||||
@ -81,7 +98,7 @@ const fieldChange = ({ index, ...value }: Record<string, any>) => {
 | 
			
		||||
    props.model[props.name].push(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addDialog.value?.hide();
 | 
			
		||||
  addDialogVisible.value = false;
 | 
			
		||||
 | 
			
		||||
  emit('change', props.model[props.name]);
 | 
			
		||||
};
 | 
			
		||||
@ -122,7 +139,8 @@ const fieldColumns: ColumnConfig[] = [
 | 
			
		||||
            index,
 | 
			
		||||
          };
 | 
			
		||||
          fieldTitle.value = `编辑${row.title}`;
 | 
			
		||||
          addDialog.value?.show();
 | 
			
		||||
          calcBoxPosition();
 | 
			
		||||
          addDialogVisible.value = true;
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
@ -220,7 +238,6 @@ const dataSourceFieldsConfig: FormConfig = [
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const addFromJsonDialog = ref<InstanceType<typeof MFormDrawer>>();
 | 
			
		||||
const jsonFromConfig: FormConfig = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'data',
 | 
			
		||||
@ -238,7 +255,8 @@ const jsonFromValues = ref({
 | 
			
		||||
 | 
			
		||||
const newFromJsonHandler = () => {
 | 
			
		||||
  jsonFromValues.value.data = getDefaultValueFromFields(props.model[props.name]);
 | 
			
		||||
  addFromJsonDialog.value?.show();
 | 
			
		||||
  calcBoxPosition();
 | 
			
		||||
  addFromJsonDialogVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getValueType = (value: any) => {
 | 
			
		||||
@ -288,11 +306,18 @@ const addFromJsonFromChange = ({ data }: { data: string }) => {
 | 
			
		||||
 | 
			
		||||
    props.model[props.name] = getFieldsConfig(value, props.model[props.name]);
 | 
			
		||||
 | 
			
		||||
    addFromJsonDialog.value?.hide();
 | 
			
		||||
    addFromJsonDialogVisible.value = false;
 | 
			
		||||
 | 
			
		||||
    emit('change', props.model[props.name]);
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    tMagicMessage.error(e.message);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addDialogVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
const addFromJsonDialogVisible = defineModel<boolean>('visible1', { default: false });
 | 
			
		||||
const { height: editorHeight } = useEditorContentHeight();
 | 
			
		||||
 | 
			
		||||
const parentFloating = inject<Ref<HTMLDivElement>>('parentFloating');
 | 
			
		||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(services?.uiService, parentFloating);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -6,32 +6,44 @@
 | 
			
		||||
      <TMagicButton size="small" type="primary" :disabled="disabled" plain @click="newHandler()">添加</TMagicButton>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <MFormDrawer
 | 
			
		||||
      ref="addDialog"
 | 
			
		||||
      label-width="120px"
 | 
			
		||||
    <FloatingBox
 | 
			
		||||
      v-model:visible="addDialogVisible"
 | 
			
		||||
      v-model:width="width"
 | 
			
		||||
      v-model:height="editorHeight"
 | 
			
		||||
      :title="drawerTitle"
 | 
			
		||||
      :config="formConfig"
 | 
			
		||||
      :values="formValues"
 | 
			
		||||
      :parentValues="model[name]"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      @submit="formChangeHandler"
 | 
			
		||||
    ></MFormDrawer>
 | 
			
		||||
      :position="boxPosition"
 | 
			
		||||
    >
 | 
			
		||||
      <template #body>
 | 
			
		||||
        <MFormBox
 | 
			
		||||
          ref="addDialog"
 | 
			
		||||
          label-width="120px"
 | 
			
		||||
          :config="formConfig"
 | 
			
		||||
          :values="formValues"
 | 
			
		||||
          :parentValues="model[name]"
 | 
			
		||||
          :disabled="disabled"
 | 
			
		||||
          @submit="formChangeHandler"
 | 
			
		||||
        ></MFormBox>
 | 
			
		||||
      </template>
 | 
			
		||||
    </FloatingBox>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, inject, ref } from 'vue';
 | 
			
		||||
import { inject, Ref, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { TMagicButton, tMagicMessageBox, TMagicSwitch } from '@tmagic/design';
 | 
			
		||||
import { type FieldProps, type FormConfig, type FormState, MFormDrawer } from '@tmagic/form';
 | 
			
		||||
import { type FieldProps, type FormConfig, type FormState, MFormBox } from '@tmagic/form';
 | 
			
		||||
import type { MockSchema } from '@tmagic/schema';
 | 
			
		||||
import { type ColumnConfig, MagicTable } from '@tmagic/table';
 | 
			
		||||
import { getDefaultValueFromFields } from '@tmagic/utils';
 | 
			
		||||
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
 | 
			
		||||
import CodeEditor from '@editor/layouts/CodeEditor.vue';
 | 
			
		||||
import { Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
import { useEditorContentHeight } from '..';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'MFieldsDataSourceMocks',
 | 
			
		||||
});
 | 
			
		||||
@ -50,9 +62,8 @@ const props = withDefaults(
 | 
			
		||||
const emit = defineEmits(['change']);
 | 
			
		||||
 | 
			
		||||
const services = inject<Services>('services');
 | 
			
		||||
const width = computed(() => globalThis.document.body.clientWidth - (services?.uiService.get('columnWidth').left || 0));
 | 
			
		||||
const width = defineModel<number>('width', { default: 670 });
 | 
			
		||||
 | 
			
		||||
const addDialog = ref<InstanceType<typeof MFormDrawer>>();
 | 
			
		||||
const drawerTitle = ref('');
 | 
			
		||||
const formValues = ref<Record<string, any>>({});
 | 
			
		||||
 | 
			
		||||
@ -182,7 +193,8 @@ const columns: ColumnConfig[] = [
 | 
			
		||||
            index,
 | 
			
		||||
          };
 | 
			
		||||
          drawerTitle.value = `编辑${row.title}`;
 | 
			
		||||
          addDialog.value?.show();
 | 
			
		||||
          calcBoxPosition();
 | 
			
		||||
          addDialogVisible.value = true;
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
@ -206,7 +218,8 @@ const newHandler = () => {
 | 
			
		||||
    enable: isFirstRow,
 | 
			
		||||
  };
 | 
			
		||||
  drawerTitle.value = '新增Mock';
 | 
			
		||||
  addDialog.value?.show();
 | 
			
		||||
  calcBoxPosition();
 | 
			
		||||
  addDialogVisible.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const formChangeHandler = ({ index, ...value }: Record<string, any>) => {
 | 
			
		||||
@ -216,7 +229,7 @@ const formChangeHandler = ({ index, ...value }: Record<string, any>) => {
 | 
			
		||||
    props.model[props.name].push(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addDialog.value?.hide();
 | 
			
		||||
  addDialogVisible.value = false;
 | 
			
		||||
 | 
			
		||||
  emit('change', props.model[props.name]);
 | 
			
		||||
};
 | 
			
		||||
@ -234,4 +247,9 @@ const toggleValue = (row: MockSchema, key: 'enable' | 'useInEditor', value: bool
 | 
			
		||||
    index,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addDialogVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
const { height: editorHeight } = useEditorContentHeight();
 | 
			
		||||
const parentFloating = inject<Ref<HTMLDivElement>>('parentFloating');
 | 
			
		||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(services?.uiService, parentFloating);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ export const useEditorContentHeight = () => {
 | 
			
		||||
 | 
			
		||||
  const height = ref(0);
 | 
			
		||||
  watchEffect(() => {
 | 
			
		||||
    if (height.value > 0) return;
 | 
			
		||||
    if (height.value > 0 && height.value === editorContentHeight.value) return;
 | 
			
		||||
    height.value = editorContentHeight.value;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
import { computed, ComputedRef, ref, watch } from 'vue';
 | 
			
		||||
import { computed, ComputedRef, inject, ref, watch } from 'vue';
 | 
			
		||||
 | 
			
		||||
import type { Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
interface State {
 | 
			
		||||
  status: boolean;
 | 
			
		||||
@ -7,6 +9,8 @@ interface State {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useFloatBox = (slideKeys: ComputedRef<string[]>) => {
 | 
			
		||||
  const services = inject<Services>('services');
 | 
			
		||||
 | 
			
		||||
  const floatBoxStates = ref<{
 | 
			
		||||
    [key in (typeof slideKeys.value)[number]]: State;
 | 
			
		||||
  }>(
 | 
			
		||||
@ -30,9 +34,10 @@ export const useFloatBox = (slideKeys: ComputedRef<string[]>) => {
 | 
			
		||||
 | 
			
		||||
  const dragstartHandler = () => (isDragging.value = true);
 | 
			
		||||
  const dragendHandler = (key: string, e: DragEvent) => {
 | 
			
		||||
    const navMenuRect = services?.uiService?.get('navMenuRect');
 | 
			
		||||
    floatBoxStates.value[key] = {
 | 
			
		||||
      left: e.clientX,
 | 
			
		||||
      top: e.clientY,
 | 
			
		||||
      top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
 | 
			
		||||
      status: true,
 | 
			
		||||
    };
 | 
			
		||||
    isDragging.value = false;
 | 
			
		||||
@ -47,12 +52,13 @@ export const useFloatBox = (slideKeys: ComputedRef<string[]>) => {
 | 
			
		||||
    () => slideKeys.value,
 | 
			
		||||
    (slideKeys) => {
 | 
			
		||||
      slideKeys.forEach((key) => {
 | 
			
		||||
        if (floatBoxStates.value[key]) return;
 | 
			
		||||
        floatBoxStates.value[key] = {
 | 
			
		||||
          status: false,
 | 
			
		||||
          top: 0,
 | 
			
		||||
          left: 0,
 | 
			
		||||
        };
 | 
			
		||||
        if (!floatBoxStates.value[key]) {
 | 
			
		||||
          floatBoxStates.value[key] = {
 | 
			
		||||
            status: false,
 | 
			
		||||
            top: 0,
 | 
			
		||||
            left: 0,
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								packages/editor/src/hooks/use-next-float-box-position.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								packages/editor/src/hooks/use-next-float-box-position.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
import { Ref, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { UiService } from '@editor/services/ui';
 | 
			
		||||
 | 
			
		||||
export const useNextFloatBoxPosition = (uiService?: UiService, parent?: Ref<HTMLDivElement>) => {
 | 
			
		||||
  const boxPosition = ref({
 | 
			
		||||
    left: 0,
 | 
			
		||||
    top: 0,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const calcBoxPosition = () => {
 | 
			
		||||
    const columnWidth = uiService?.get('columnWidth');
 | 
			
		||||
    const navMenuRect = uiService?.get('navMenuRect');
 | 
			
		||||
    let left = columnWidth?.left ?? 0;
 | 
			
		||||
    if (parent?.value) {
 | 
			
		||||
      const rect = parent?.value?.getBoundingClientRect();
 | 
			
		||||
      left = (rect?.left ?? 0) + (rect?.width ?? 0);
 | 
			
		||||
    }
 | 
			
		||||
    boxPosition.value = {
 | 
			
		||||
      left,
 | 
			
		||||
      top: (navMenuRect?.top ?? 0) + (navMenuRect?.height ?? 0),
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    boxPosition,
 | 
			
		||||
    calcBoxPosition,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@ -111,8 +111,8 @@
 | 
			
		||||
        :key="config.$key ?? index"
 | 
			
		||||
        v-if="floatBoxStates[config.$key]?.status"
 | 
			
		||||
        v-model:visible="floatBoxStates[config.$key].status"
 | 
			
		||||
        :width="columnLeftWitch"
 | 
			
		||||
        :height="600"
 | 
			
		||||
        v-model:width="columnLeftWidth"
 | 
			
		||||
        v-model:height="columnLeftHeight"
 | 
			
		||||
        :title="config.text"
 | 
			
		||||
        :position="{
 | 
			
		||||
          left: floatBoxStates[config.$key].left,
 | 
			
		||||
@ -123,8 +123,8 @@
 | 
			
		||||
          <div class="m-editor-slide-list-box">
 | 
			
		||||
            <component
 | 
			
		||||
              v-if="config && floatBoxStates[config.$key].status"
 | 
			
		||||
              :is="config.boxComponentConfig?.component || config.component"
 | 
			
		||||
              v-bind="config.boxComponentConfig?.props || config.props || {}"
 | 
			
		||||
              :is="config.component"
 | 
			
		||||
              v-bind="config.props || {}"
 | 
			
		||||
              v-on="config?.listeners || {}"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
@ -140,6 +140,7 @@ import { Coin, EditPen, Goods, List } from '@element-plus/icons-vue';
 | 
			
		||||
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import MIcon from '@editor/components/Icon.vue';
 | 
			
		||||
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
 | 
			
		||||
import { useFloatBox } from '@editor/hooks/use-float-box';
 | 
			
		||||
import {
 | 
			
		||||
  ColumnLayout,
 | 
			
		||||
@ -176,7 +177,8 @@ const props = withDefaults(
 | 
			
		||||
 | 
			
		||||
const services = inject<Services>('services');
 | 
			
		||||
 | 
			
		||||
const columnLeftWitch = computed(() => services?.uiService.get('columnWidth')[ColumnLayout.LEFT] || 0);
 | 
			
		||||
const columnLeftWidth = computed(() => services?.uiService.get('columnWidth')[ColumnLayout.LEFT] || 0);
 | 
			
		||||
const { height: columnLeftHeight } = useEditorContentHeight();
 | 
			
		||||
 | 
			
		||||
const activeTabName = ref(props.data?.status);
 | 
			
		||||
 | 
			
		||||
@ -209,11 +211,6 @@ const getItemConfig = (data: SideItem): SideComponent => {
 | 
			
		||||
      text: '代码编辑',
 | 
			
		||||
      component: CodeBlockListPanel,
 | 
			
		||||
      slots: {},
 | 
			
		||||
      boxComponentConfig: {
 | 
			
		||||
        props: {
 | 
			
		||||
          slideType: 'box',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    'data-source': {
 | 
			
		||||
      $key: 'data-source',
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,6 @@
 | 
			
		||||
    ref="codeBlockEditor"
 | 
			
		||||
    :disabled="!editable"
 | 
			
		||||
    :content="codeConfig"
 | 
			
		||||
    :slideType="slideType"
 | 
			
		||||
    @submit="submitCodeBlockHandler"
 | 
			
		||||
  ></CodeBlockEditor>
 | 
			
		||||
</template>
 | 
			
		||||
@ -37,7 +36,7 @@ import type { Id } from '@tmagic/schema';
 | 
			
		||||
import CodeBlockEditor from '@editor/components/CodeBlockEditor.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';
 | 
			
		||||
import type { CodeBlockListPanelSlots, CodeDeleteErrorType, Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
import CodeBlockList from './CodeBlockList.vue';
 | 
			
		||||
 | 
			
		||||
@ -49,7 +48,6 @@ defineOptions({
 | 
			
		||||
 | 
			
		||||
defineProps<{
 | 
			
		||||
  customError?: (id: Id, errorType: CodeDeleteErrorType) => any;
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const { codeBlockService } = inject<Services>('services') || {};
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,37 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <component
 | 
			
		||||
    :is="slideType === 'box' ? MFormBox : MFormDrawer"
 | 
			
		||||
    ref="fomDrawer"
 | 
			
		||||
    label-width="80px"
 | 
			
		||||
    :close-on-press-escape="false"
 | 
			
		||||
  <FloatingBox
 | 
			
		||||
    v-model:visible="boxVisible"
 | 
			
		||||
    v-model:width="width"
 | 
			
		||||
    v-model:height="editorHeight"
 | 
			
		||||
    :title="title"
 | 
			
		||||
    :width="size"
 | 
			
		||||
    :config="dataSourceConfig"
 | 
			
		||||
    :values="initValues"
 | 
			
		||||
    :disabled="disabled"
 | 
			
		||||
    @submit="submitHandler"
 | 
			
		||||
    @error="errorHandler"
 | 
			
		||||
  ></component>
 | 
			
		||||
    :position="boxPosition"
 | 
			
		||||
  >
 | 
			
		||||
    <template #body>
 | 
			
		||||
      <MFormBox
 | 
			
		||||
        label-width="80px"
 | 
			
		||||
        :title="title"
 | 
			
		||||
        :config="dataSourceConfig"
 | 
			
		||||
        :values="initValues"
 | 
			
		||||
        :disabled="disabled"
 | 
			
		||||
        style="height: 100%"
 | 
			
		||||
        @submit="submitHandler"
 | 
			
		||||
        @error="errorHandler"
 | 
			
		||||
      ></MFormBox>
 | 
			
		||||
    </template>
 | 
			
		||||
  </FloatingBox>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, inject, ref, watchEffect } from 'vue';
 | 
			
		||||
import { inject, Ref, ref, watchEffect } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { tMagicMessage } from '@tmagic/design';
 | 
			
		||||
import { FormConfig, MFormBox, MFormDrawer } from '@tmagic/form';
 | 
			
		||||
import { FormConfig, MFormBox } from '@tmagic/form';
 | 
			
		||||
import { DataSourceSchema } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
import type { Services, SlideType } from '@editor/type';
 | 
			
		||||
import FloatingBox from '@editor/components/FloatingBox.vue';
 | 
			
		||||
import { useEditorContentHeight } from '@editor/hooks';
 | 
			
		||||
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
 | 
			
		||||
import type { Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: 'MEditorDataSourceConfigPanel',
 | 
			
		||||
@ -31,23 +41,27 @@ const props = defineProps<{
 | 
			
		||||
  title?: string;
 | 
			
		||||
  values: any;
 | 
			
		||||
  disabled: boolean;
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const boxVisible = defineModel<boolean>('visible', { default: false });
 | 
			
		||||
const width = defineModel<number>('width', { default: 670 });
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['submit']);
 | 
			
		||||
 | 
			
		||||
const services = inject<Services>('services');
 | 
			
		||||
 | 
			
		||||
const size = computed(() => globalThis.document.body.clientWidth - (services?.uiService.get('columnWidth').left || 0));
 | 
			
		||||
 | 
			
		||||
const fomDrawer = ref<InstanceType<typeof MFormDrawer>>();
 | 
			
		||||
 | 
			
		||||
const initValues = ref<Partial<DataSourceSchema>>({});
 | 
			
		||||
const dataSourceConfig = ref<FormConfig>([]);
 | 
			
		||||
 | 
			
		||||
const { height: editorHeight } = useEditorContentHeight();
 | 
			
		||||
 | 
			
		||||
const parentFloating = inject<Ref<HTMLDivElement>>('parentFloating');
 | 
			
		||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(services?.uiService, parentFloating);
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  initValues.value = props.values;
 | 
			
		||||
  dataSourceConfig.value = services?.dataSourceService.getFormConfig(initValues.value.type) || [];
 | 
			
		||||
  console.log(dataSourceConfig.value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const submitHandler = (values: any) => {
 | 
			
		||||
@ -60,11 +74,12 @@ const errorHandler = (error: any) => {
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  show() {
 | 
			
		||||
    fomDrawer.value?.show();
 | 
			
		||||
    calcBoxPosition();
 | 
			
		||||
    boxVisible.value = true;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  hide() {
 | 
			
		||||
    fomDrawer.value?.hide();
 | 
			
		||||
    boxVisible.value = false;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,6 @@
 | 
			
		||||
    :disabled="!editable"
 | 
			
		||||
    :values="dataSourceValues"
 | 
			
		||||
    :title="dialogTitle"
 | 
			
		||||
    :slideType="slideType"
 | 
			
		||||
    @submit="submitDataSourceHandler"
 | 
			
		||||
  ></DataSourceConfigPanel>
 | 
			
		||||
</template>
 | 
			
		||||
@ -47,7 +46,7 @@ import type { DataSourceSchema } from '@tmagic/schema';
 | 
			
		||||
 | 
			
		||||
import SearchInput from '@editor/components/SearchInput.vue';
 | 
			
		||||
import ToolButton from '@editor/components/ToolButton.vue';
 | 
			
		||||
import type { DataSourceListSlots, Services, SlideType } from '@editor/type';
 | 
			
		||||
import type { DataSourceListSlots, Services } from '@editor/type';
 | 
			
		||||
 | 
			
		||||
import DataSourceConfigPanel from './DataSourceConfigPanel.vue';
 | 
			
		||||
import DataSourceList from './DataSourceList.vue';
 | 
			
		||||
@ -58,10 +57,6 @@ defineOptions({
 | 
			
		||||
  name: 'MEditorDataSourceListPanel',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineProps<{
 | 
			
		||||
  slideType?: SlideType;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const { dataSourceService } = inject<Services>('services') || {};
 | 
			
		||||
 | 
			
		||||
const editDialog = ref<InstanceType<typeof DataSourceConfigPanel>>();
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,3 @@
 | 
			
		||||
    display: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.m-editor-code-block-editor {
 | 
			
		||||
  .tmagic-design-table {
 | 
			
		||||
    height: 180px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-drawer__body {
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -401,12 +401,6 @@ export interface SideBarData {
 | 
			
		||||
  items: SideItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * drawer 抽屉
 | 
			
		||||
 * box 悬浮窗
 | 
			
		||||
 */
 | 
			
		||||
export type SlideType = 'drawer' | 'box';
 | 
			
		||||
 | 
			
		||||
export interface ComponentItem {
 | 
			
		||||
  /** 显示文案 */
 | 
			
		||||
  text: string;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user