vue3中左右布局两个个组件使用vuedraggable实现左向右拖动,右组件列表可上下拖动

发布于:2025-04-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

需求:左侧是个菜单组件,有对应的表单类型。
右侧是渲染组件,点击左侧菜单或者拖动即可渲染出对应的组件
项目中采用vuedraggable实现拖拽功能。

具体实现是使用elementplus的组件,然后根据tagName的类型去渲染不同的组件。

首先是大的组件包含左右的盒子组件,代码如下:

   <div class="iform_editor">
    <div class="iform_editor_menu">
      <FormMenuDrag  :formType="templateConf.type" @scroll-to-top="scrollToTop" />
    </div>
    <div class="iform_editor_content">
      <ElScrollbar ref="renderScrollBar" class="iform_editor_content_sw">
        <el-backtop
          target=".iform_editor_content_sw .el-scrollbar__wrap"
          :right="300"
          :bottom="50"
        />
        <FormRenderDrag  @form-publish="formPublish" :formType="templateConf.type" />
      </ElScrollbar>
    </div>
  </div>

左侧菜单组件代码如下:

  <div class="form_menu_drag">
    <ElScrollbar v-loading="false">
      <div class="component_menu_list" v-for="(item, keyIndex) in formItemConf" :key="keyIndex">
        <h4 class="tool_bar_title">{{ item.groupTitle }}</h4>
        <draggable
          :list="item.groupList"
          class="tool_item_list"
          :group="{
            name: 'componentsGroup',
            pull: 'clone',
            put: false,
          }"
          :clone="handleClone"
          :sort="false"
          :item-key="'renderKey'"
        >
          <template #item="{ element, index }">
            <div class="tool_item">
              <ElPopover
                placement="right"
                trigger="hover"
                width="300"
                :offset="index % 2 == 0 ? 150 : 12"
                :disabled="!config.explainList[element.customConf.label]"
              >
                <template #reference>
                  <div
                    class="tool_item_drag"
                    :class="{ disabled: checkItem(element?.customConf?.tagName || '') }"
                    @click.stop="handleAdd(element)"
                  >
                    <Icon class="tool_item_icon" :icon="`svg-icon:${element.customConf.tagIcon}`" />
                    {{ element.customConf.label }}
                  </div>
                </template>
                <FormItemExplainCard
                  v-if="config.explainList[element.customConf.label]"
                  :explain="config.explainList[element.customConf.label]"
                />
              </ElPopover>
            </div>
          </template>
        </draggable>
      </div>
    </ElScrollbar>
  </div>
<script setup lang="ts">
import { ref } from 'vue';
import draggable from 'vuedraggable';
import EditorUtils from '@/utils/Editor_utils';
import { Icon } from '@/components/Icon';
import { IFormModel } from 'Editor';
import { useEditorStore } from '@/store/modules/iform/editor';
import FormItemExplainCard from './form_item_explain_card.vue';
import config from '@/config/iform_editor_config';
import { storeToRefs } from 'pinia';
import { cloneDeep } from 'lodash-es';

const editorStore = useEditorStore();
let { formDataList } = storeToRefs(editorStore);
const emit = defineEmits(['scroll-to-top']);

interface formGroupList {
  groupTitle: string;
  groupList: IFormModel.formItemConf[];
}
const props = defineProps<{ formType: number }>();
const formType = ref(props.formType);

const commonList = [
  {
    groupTitle: '选择',
    groupList: EditorUtils.getDefaultItemConfig('select'),
  },
  {
    groupTitle: '文本',
    groupList: EditorUtils.getDefaultItemConfig('text'),
  },
];

const assessmentList = [
  {
    groupTitle: '考前准备',
    groupList: EditorUtils.getDefaultItemConfig('assessmentPreparation'),
  },
  {
    groupTitle: '常用题型',
    groupList: EditorUtils.getDefaultItemConfig('assessmentCommon'),
  },
];

const formItemConf = ref<formGroupList[]>(formType.value == 2 ? assessmentList : commonList);

const handleClone = (e) => {
  return cloneDeep(e);
};

const handleAdd = (e) => {
  const item = cloneDeep(e);
  const tagName = item.customConf.tagName;
  editorStore.addFormItem(tagName, '', undefined, true);
  editorStore.setFormSetting(false);
  nextTick(() => {
    const newDom = document.querySelector('.drag_item.isActive');
    newDom?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  });
};

const checkItem = (tagName: string) => {
  if (
    tagName == 'el-assessment-instructions' ||
    tagName == 'el-assessment-userinfo' ||
    tagName == 'el-assessment-cascader' ||
    tagName == 'el-assessment-text'
  ) {
    return unref(formDataList)?.find((item) => {
      return item?.customConf?.tagName == tagName;
    });
  } else {
    return false;
  }
};
</script>

中间组件内容:

<template>
       <draggable
          class="draggable_wrap"
          :class="{ hide: dragFormDataList.length < 1 && !isAni }"
          :group="{
            name: 'componentsGroup',
          }"
          :list="dragFormDataList"
          :handle="'.handle_bar'"
          :item-key="'renderKey'"
          :sort="true"
          @change="draggableChange"
        >
          <template #item="{ element, index }">
            <div
              class="drag_item"
              :class="{ isActive: activeKey == element.renderKey }"
              @click.stop="selectItem(element.renderKey)"
              :data-key="element.renderKey"
              :id="element.formItemKey"
            >
              <DragToolBar
                v-show="activeKey == element.renderKey"
                :labelName="element?.customConf?.label || ''"
                :tagName="element?.customConf?.tagName || ''"
                :renderKey="element.renderKey"
                :customConf="element?.customConf"
                :formType="formType"
                @trigger-copy="
                  onCopyFn(element?.customConf?.tagName || '', element.formItemKey, index + 1)
                "
                @trigger-del="onDeleteFn(element.formItemKey, index)"
                @trigger-change="(type) => onChangeFn(type, element.formItemKey)"
              />
              <div class="item_content">
                <FormReducer
                  :sn="handleFormat(element.baseConf.numType, index, element.formItemKey)"
                  :formItemData="element"
                />
              </div>
            </div>
          </template>
        </draggable>
   </template>

FormReducer组件的内容:

<script lang="ts">
// placeholders里边都是写好的各个类型的组件,直接引入。
import BaseFormItem from './components/base_from_item.vue';
import {
  PInput,
} from './components/placeholders';
import { tagTypeEnum } from '@/types/option_enum';
import { IFormModel } from 'Editor';
import type { PropType } from 'vue';

export default defineComponent({
  props: {
    sn: {
      type: Number,
      required: true,
    },
    formItemData: {
      type: Object as PropType<IFormModel.formItemConf>,
      required: true,
    },
  },
  emits: ['updateMsg'],
  setup(props) {
    const currentData = ref(props.formItemData);
    watch(
      () => props.formItemData,
      () => {
        currentData.value = props.formItemData;
      },
    );
    // const currentData = ref<IFormModel.formItemConf>(props.formItemData);
    if (unref(currentData).customConf) {
      switch (unref(currentData).customConf.tagType) {
        case tagTypeEnum.INPUT: {
          return () => {
            return h(
              BaseFormItem,
              {
                sn: props.sn,
                renderKey: unref(currentData)?.renderKey || '',
              },
              () => [
                h(PInput, {
                  renderKey: unref(currentData)?.renderKey || '',
                }),
              ],
            );
          };
        }
        case tagTypeEnum.INPUTGROUP: {
          return () => {
            return h(
              BaseFormItem,
              {
                sn: props.sn,
                renderKey: unref(currentData)?.renderKey || '',
              },
              () => [
                h(PInputGroup, {
                  renderKey: unref(currentData)?.renderKey || '',
                }),
              ],
            );
          };
        }
        default: {
          return () => {
            return h('');
          };
        }
      }
    }
  },
});
</script>

已上就是整个实现过程啦!


网站公告

今日签到

点亮在社区的每一天
去签到