说一下我的需求吧,我这边是要实现一个类似钉钉那种行政管理模块,需要开发一个可以设置多套表单模板的动态表单;基本情况就是这样,好了,我们直接开始吧

表单设计区域拖拽排序,表单设置补全内容,这三项代码配置即可,我只简单的配置了几项

首先第一步  安装Vue.Draggable

我这里使用的是draggable实现拖拽

draggable简介

Vue.Draggable 是一个基于 Vue.js 的拖放组件,它允许用户在 Vue.js 应用中实现拖放功能。该项目是基于 Sortable.js 构建的,提供了丰富的功能和灵活的配置选项,适用于各种拖放场景。

draggable的使用文档可以参考vue.draggable中文文档 - itxst.com

npm install vuedraggable
# 或者使用 yarn
yarn add vuedraggable

安装完成之后就可以开始使用了,在需要使用的页面引入draggable

vue2需要用components注册组件

import draggable from 'vuedraggable';


components: { draggable, },

然后就是开始使用了,在需要使用到拖拽的内容进行包裹

 <draggable v-model="formComponents" @end="onDragEnd" handle=".handle">
                            <transition-group>
                                <div v-for="(component, index) in formComponents" :key="index" class="form-component"
                                    :class="{ 'selected': selectedComponent === component, 'hover-effect': true }"
                                    @click="selectComponent(component)">
                                    <div class="handle">
                                        <a-row type="flex" justify="space-between" align="middle">
                                            <a-col :span="6">
                                                <span>{{ component.props.label || '未命名' }}</span>
                                            </a-col>
                                            <a-col :span="14">
                                                <component :is="component.type" v-bind="component.props" />
                                            </a-col>
                                            <a-col :span="4">
                                                <a-popconfirm title="确定删除当前控件吗?" ok-text="确定" cancel-text="取消"
                                                    @confirm="removeComponent(index)">
                                                    <a-button type="link" v-if="formComponents.length > 1">删除</a-button>
                                                </a-popconfirm>
                                            </a-col>
                                        </a-row>
                                    </div>
                                </div>
                            </transition-group>
                        </draggable>

组件库我使用的是动态组件生产这样的话好进行配置

html代码
               <a-card title="组件库">
                        <a-button class="atd-btn" v-for="item in assemblyData" :key="item.key" :icon="item.icon"
                            type="dashed" @click="addComponent(item.key)">{{
                                item.value }}</a-button>
                    </a-card>

        addComponent(type) {
            const component = {
                type: '',
                props: {},
                fieldTitle: '',
                dataInfType: undefined,//select数据类型
            };

            switch (type) {
                case 'TEXT':
                    component.type = 'a-input';
                    component.fieldTitle = 'TEXT';
                    component.props = { placeholder: '请输入', label: '文本框', style: { width: '100%' }, required: true };
                    break;
                case 'LIST':
                    component.type = 'a-select';
                    component.fieldTitle = 'LIST';
                    component.props = { placeholder: '请选择', label: '选择框', style: { width: '100%' }, required: true };
                    break;
                case 'NUMBER':
                    component.type = 'a-input-number';
                    component.fieldTitle = 'NUMBER';
                    component.props = { placeholder: '请输入', label: '数字输入', style: { width: '100%' }, required: true };
                    break;
                case 'MONEY':
                    component.type = 'a-input-number';
                    component.fieldTitle = 'MONEY';
                    component.props = { placeholder: '请输入', label: '金额输入', style: { width: '100%' }, step: 0.01, required: true };
                    break;
                case 'DATE':
                    component.type = 'a-date-picker';
                    component.fieldTitle = 'DATE';
                    component.props = { placeholder: '请选择', label: '日期', style: { width: '100%' }, required: true };
                    break;
                default:
                    break;
            }

            this.formComponents.push(component);
            this.selectComponent(component);
        },

整体实现

<template>
    <a-modal title="获取审批表单详情" width="80%" :visible="visible" :confirm-loading="confirmLoading" @ok="submitClick"
        @cancel="closeCancel">
        <a-row>
            <a-col :span="8">
                <a-form-model-item ref="name" label="审批表单名称" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
                    <a-input v-model="dataTemp.approveName" />
                </a-form-model-item>
            </a-col>
            <a-col :span="8">
                <a-form-model-item ref="name" label="抄送人类型" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"
                    prop="name">
                    <a-select v-model="dataTemp.noticeType" style="width: 100%" placeholder="请选择"
                        option-filter-prop="children" show-search :allowClear="true" :filter-option="filterOption">
                        <a-select-option v-for="item in recipientTypeData" :key="item.key" :value="item.key"
                            :label="item.value">{{
                                item.value }}</a-select-option>
                    </a-select>
                </a-form-model-item>
            </a-col>
            <a-col :span="8">
                <a-form-model-item ref="name" v-if="dataTemp.noticeType == 'ORDER_ROLE'" label="指定角色选择"
                    :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" prop="name">
                    <a-select mode="multiple" v-model="dataTemp.roleIdList" style="width: 100%" placeholder="请选择领用人"
                        option-filter-prop="children" show-search :allowClear="true" :filter-option="filterOption">
                        <a-select-option v-for="item in roleData" :key="item.id" :value="item.id" :label="item.roleName">{{
                            item.roleName }}</a-select-option>
                    </a-select>
                </a-form-model-item>
                <a-form-model-item ref="name" v-if="dataTemp.noticeType == 'ORDER_PERSON'" label="指定人员选择"
                    :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }" prop="name">
                    <a-select mode="multiple" v-model="dataTemp.employeeIdList" style="width: 100%" placeholder="请选择领用人"
                        option-filter-prop="children" show-search :allowClear="true" :filter-option="filterOption">
                        <a-select-option v-for="item in allemployee" :key="item.id" :value="item.id"
                            :label="item.actualName">{{ item.actualName }}</a-select-option>
                    </a-select>
                </a-form-model-item>
            </a-col>
        </a-row>
        <div class="form-designer">
            <a-row :gutter="16">
                <a-col :span="6">
                    <a-card title="组件库">
                        <a-button class="atd-btn" v-for="item in assemblyData" :key="item.key" :icon="item.icon"
                            type="dashed" @click="addComponent(item.key)">{{
                                item.value }}</a-button>
                    </a-card>
                </a-col>
                <a-col :span="12">
                    <a-card title="表单设计区">
                        <draggable v-model="formComponents" @end="onDragEnd" handle=".handle">
                            <transition-group>
                                <div v-for="(component, index) in formComponents" :key="index" class="form-component"
                                    :class="{ 'selected': selectedComponent === component, 'hover-effect': true }"
                                    @click="selectComponent(component)">
                                    <div class="handle">
                                        <a-row type="flex" justify="space-between" align="middle">
                                            <a-col :span="6">
                                                <span>{{ component.props.label || '未命名' }}</span>
                                            </a-col>
                                            <a-col :span="14">
                                                <component :is="component.type" v-bind="component.props" />
                                            </a-col>
                                            <a-col :span="4">
                                                <a-popconfirm title="确定删除当前控件吗?" ok-text="确定" cancel-text="取消"
                                                    @confirm="removeComponent(index)">
                                                    <a-button type="link" v-if="formComponents.length > 1">删除</a-button>
                                                </a-popconfirm>
                                            </a-col>
                                        </a-row>
                                    </div>
                                </div>
                            </transition-group>
                        </draggable>
                    </a-card>
                </a-col>
                <a-col :span="6">
                    <a-card title="表单设置">
                        <a-form v-if="selectedComponent">
                            <a-form-item label="名称">
                                <a-input v-model="selectedComponent.props.label" />
                            </a-form-item>
                            <a-form-item label="提示文字">
                                <a-input v-model="selectedComponent.props.placeholder" />
                            </a-form-item>
                            <a-form-item v-if="selectedComponent.type === 'a-select'" label="选择数据来源">
                                <a-select v-model="selectedComponent.dataInfType" style="width: 100%" placeholder="请选择"
                                    option-filter-prop="children" show-search :allowClear="true"
                                    :filter-option="filterOption" @change="getChangeSelect">
                                    <a-select-option v-for="item in selectData" :key="item.key" :value="item.key"
                                        :label="item.value">{{ item.value }}</a-select-option>
                                </a-select>
                            </a-form-item>
                            <a-form-item label="是否必填">
                                <a-switch v-model="selectedComponent.props.required" />
                            </a-form-item>
                        </a-form>
                    </a-card>
                </a-col>
            </a-row>
        </div>
    </a-modal>
</template>

<script>
import draggable from 'vuedraggable';

export default {
    components: { draggable, },
    props: {
        dataTemp: {
            type: Array,
        },
        visibleEdit: {
            type: Boolean,
        },
    },
    data() {
        return {
            formComponents: [],
            allemployee: [],
            roleData: [],
            visible: false,
            selectedComponent: null,
            recipientTypeData: [{ key: 'SELF', value: '发起人自己' },
            { key: 'ORDER_PERSON', value: '指定人员' },
            { key: 'ORDER_ROLE', value: '指定角色' }, { key: 'DEP_LEADER', value: '部门主管' }, { key: 'CENTER_LEADER', value: '中心负责人' },],//抄送人类型
            assemblyData: [{ key: 'TEXT', value: '输入框', icon: 'file-text' }, { key: 'LIST', value: '选择器', icon: 'select' }, { key: 'NUMBER', value: '数字框', icon: 'number' }, { key: 'DATE', value: '日期选择', icon: 'carry-out' }, { key: 'MONEY', value: '金额输入', icon: 'money-collect' }],
            selectData: [{ key: 'EMP', value: '通讯录员工' }, { key: 'DEP', value: '部门' }, { key: 'STORE', value: '仓库' }, { key: 'ADMIN', value: '仓库管理员' }, { key: 'ASSET', value: '资产' },],
        };
    },
    watch: {
        visibleEdit(oldVal, newVal) {
            this.visible = true
            console.log(this.dataTemp.fieldVOS);
            this.dataTemp.noticeType = this.dataTemp.noticeType.name
            //重置数据类型
            if (this.dataTemp.employeeIdList) {
                this.dataTemp.employeeIdList = this.dataTemp.employeeIdList.map(item => parseInt(item, 10));
            } else {
                this.dataTemp.employeeIdList = []
            }
            if (this.dataTemp.roleIdList) {
                this.dataTemp.roleIdList = this.dataTemp.roleIdList.map(item => parseInt(item, 10));
            } else {
                this.dataTemp.roleIdList = []
            }
            //获取员工接口
            this.getselects()
            //获取所有角色
            this.getRoleDataList()
            let typeDate = { TEXT: 'a-input', LIST: 'a-select', NUMBER: 'a-input-number', DATE: 'a-date-picker', MONEY: 'a-input-number' }
            let requirdData = { 0: false, 1: true }
            if (this.dataTemp.fieldVOS.length > 0) {
                //回显的数据处理
                this.formComponents = this.dataTemp.fieldVOS
                this.formComponents.forEach(item => {
                    if (item.dataType) {
                        item.dataInfType = item.dataType.name
                    }
                    item.fieldTitle = item.fieldType.name,
                        item.type = typeDate[item.fieldType.name],
                        item.props = { label: item.fieldName, placeholder: item.tip, required: requirdData[item.required], style: { width: '100%' } }
                });
            }
            //处理流程,接口好上传,这里是用不到的
            this.dataTemp.nodeVOS.forEach(item => {
                item.approveType = item.approveType.name
            })
            console.log(this.formComponents);
        }
    },
    methods: {
        filterOption(input, option) {
            return (
                option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            )
        },
        getChangeSelect(value) {
            //视图不更新,强制更新视图
            this.$nextTick(() => {
                this.$forceUpdate();
            })
        },
        //点击取消
        closeCancel() {
            this.visible = false
            this.formComponents = []
            this.selectedComponent = null
        },
        addComponent(type) {
            const component = {
                type: '',
                props: {},
                fieldTitle: '',
                dataInfType: undefined,//select数据类型
            };

            switch (type) {
                case 'TEXT':
                    component.type = 'a-input';
                    component.fieldTitle = 'TEXT';
                    component.props = { placeholder: '请输入', label: '文本框', style: { width: '100%' }, required: true };
                    break;
                case 'LIST':
                    component.type = 'a-select';
                    component.fieldTitle = 'LIST';
                    component.props = { placeholder: '请选择', label: '选择框', style: { width: '100%' }, required: true };
                    break;
                case 'NUMBER':
                    component.type = 'a-input-number';
                    component.fieldTitle = 'NUMBER';
                    component.props = { placeholder: '请输入', label: '数字输入', style: { width: '100%' }, required: true };
                    break;
                case 'MONEY':
                    component.type = 'a-input-number';
                    component.fieldTitle = 'MONEY';
                    component.props = { placeholder: '请输入', label: '金额输入', style: { width: '100%' }, step: 0.01, required: true };
                    break;
                case 'DATE':
                    component.type = 'a-date-picker';
                    component.fieldTitle = 'DATE';
                    component.props = { placeholder: '请选择', label: '日期', style: { width: '100%' }, required: true };
                    break;
                default:
                    break;
            }

            this.formComponents.push(component);
            this.selectComponent(component);
        },
        removeComponent(index) {
            this.formComponents.splice(index, 1);
            if (this.selectedComponent && this.formComponents.indexOf(this.selectedComponent) === -1) {
                this.selectedComponent = null;
            }
        },
        onDragEnd(event) {
            // 拖拽结束后的处理逻辑
        },
        selectComponent(component) {
            this.selectedComponent = component;
        },
        //获取所有角色
        getRoleDataList() {
            this.$api.getAllrole().then(res => {
                if (res.code == 1) {
                    this.roleData = res.data.flatMap(item => item.roleList);
                    console.log(this.roleData);
                }
            })
        },
        //获取全部员工
        getselects() {
            this.$api.employeegetall().then(res => {
                if (res.code == 1) {
                    this.allemployee = res.data
                    console.log(this.allemployee);
                }
            })
        },
        //点击提交
        submitClick() {
            if (!this.dataTemp.approveName) {
                return this.$message.warning('请完善审批表单名称!')
            }
            if (!this.dataTemp.noticeType) {
                return this.$message.warning('请完善抄送人类型!')
            }
            if (this.dataTemp.noticeType == 'ORDER_PERSON' && this.dataTemp.employeeIdList.length == 0) {
                return this.$message.warning('请选择抄送人员!')
            }
            if (this.dataTemp.noticeType == 'ORDER_ROLE' && this.dataTemp.roleIdList.length == 0) {
                return this.$message.warning('请选择指定角色!')
            }
            if (this.formComponents.length == 0) {
                return this.$message.warning('表单设计区至少选择一项!')
            }
            let data = []
            let requirdData = { false: 0, true: 1 }
            let soutNumber = 1
            this.formComponents.forEach(item => {
                data.push({
                    id: item.id,
                    approveBasicId: this.dataTemp.id,
                    fieldName: item.props.label,
                    required: requirdData[item.props.required],
                    sortNum: soutNumber++,
                    tip: item.props.placeholder,
                    fieldType: item.fieldTitle,
                    operatorId: JSON.parse(localStorage.getItem('UserLoginInfo')).id,//创建人
                    dataType: item.dataInfType
                })
            })
            // 检查是否有 a-input 类型且 dataType 为空的情况
            console.log(this.formComponents);
            const hasInvalidInput = this.formComponents.some(item => item.type === 'a-select' && !item.dataInfType);
            if (hasInvalidInput) {
                return this.$message.warning('请将下拉选择框的数据来源补充完整!')
            }
            this.dataTemp.fieldVOS = data
            console.log(this.dataTemp);
            this.$api.getFullUpdateAPI(this.dataTemp).then(res => {
                if (res.success) {
                    this.$message.success('保存成功')
                    this.closeCancel()
                } else {
                    this.$message.error(res.msg)
                }
            })
        },
    },
};
</script>

<style scoped lang="less">
.form-designer {
    padding: 20px;
}

.atd-btn {
    margin: 0 10px 10px 0;
}

.form-component {
    margin-bottom: 10px;
    cursor: move;
    transition: transform 0.5s, box-shadow 0.5s;
    /* 添加过渡效果 */
}

.handle {
    cursor: move;
}

.selected {
    border: 1.5px solid #1890ff;
    padding: 5px;
    /* 蓝色边框 */
}

/* 鼠标经过时的样式 */
.hover-effect:hover {
    transform: scale(1.05);
    /* 放大效果 */
    box-shadow: 0 4px 4px rgba(0, 0, 0, 0.1);
    /* 高亮效果 */
}
</style>

整体代码就在这里了,如果有需要的可以试一下,把接口的数据自己换一下,我这个为了传参数据处理写的比较繁琐,哈哈哈

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐