需求:左侧是个菜单组件,有对应的表单类型。
右侧是渲染组件,点击左侧菜单或者拖动即可渲染出对应的组件
项目中采用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>
已上就是整个实现过程啦!