表单设计器拖拽对象时添加属性

news2025/7/27 7:55:41

背景:因为项目需要。自写设计器。遇到的坑在此记录

  • 使用的拖拽组件时vuedraggable。下面放上局部示例截图。
    在这里插入图片描述
  • 坑1。draggable标签在拖拽时可以获取到被拖拽的对象属性定义
    1. 要使用 :clone, 而不是@clone。我想应该是因为draggable标签比较特。
    2. 另外在使用**:clone时要将此响应添加到from draggable标签中,也就是如果从左向右拖拽标签复制,要放到左侧标签中才会起作用。意思是在赋值时对被赋值的组件对象加工处理后才会被放到右侧标签**
<template>
  <div class="form-designer">
    <!-- 左侧面板:可拖拽的控件 -->
    <div class="component-panel">
      <h3>控件库</h3>
      <draggable
        :list="availableComponents"
        :group="{ name: 'components', pull: 'clone' }"
        :clone="handleComponentClone">
        <div v-for="(comp, index) in availableComponents" :key="index" class="component-item">
          {{ comp.label }}
        </div>
      </draggable>
    </div>

    <!-- 中间画布:设计区域 -->
    <div class="design-canvas">
      <h3>表单设计区</h3>
      <draggable
        v-model="formStructure"
        :group="{ name: 'components', put: true }"
        @add="(item) => onComponentAdded(item)">
        <transition-group name="fade" tag="div" class="form-items">
          <div
            v-for="item in formStructure"
            :key="item.id"
            class="form-item"
            :class="{ selected: selectedItem === item }"
            @click="selectComponent(item)">
            <el-form>
              <component
                :is="item.type"
                v-model="formData[item.vModel]"
                :label="item.label"
                :options="item.options"
                :required="item.required" />
            </el-form>

            <!-- {{ item.label }} -->
          </div>
        </transition-group>
      </draggable>
    </div>

    <!-- 右边配置面板:属性编辑 -->
    <div class="config-panel">
      <h3>属性设置</h3>
      <div v-if="selectedItem">
        <el-form label-position="top">
          <el-form-item label="字段名称">
            <el-input v-model="selectedItem.label" />
          </el-form-item>
          <el-form-item label="绑定字段名">
            <el-input v-model="selectedItem.vModel" />
          </el-form-item>
          <el-form-item label="默认值">
            <el-input v-model="formData[selectedItem.vModel]" />
          </el-form-item>
          <el-form-item label="是否必填">
            <el-switch v-model="selectedItem.required" />
          </el-form-item>
        </el-form>
      </div>
    </div>
  </div>
</template>

<script>
import draggable from 'vuedraggable';
import DynamicInput from './components/Input.vue';
import DynamicSelect from './components/Select.vue';
import DynamicCheckboxGroup from './components/CheckboxGroup.vue';
import DynamicDatePicker from './components/DatePicker.vue';
import DynamicUpload from './components/UploadFile.vue';
import DynamicImageUpload from './components/UploadImage.vue';
import { options } from 'runjs';
import { start } from 'nprogress';
import cloneDeep from 'lodash.clonedeep';

export default {
  name: 'FormDesigner',
  components: {
    draggable,
    DynamicInput,
    DynamicSelect,
    DynamicCheckboxGroup,
    DynamicDatePicker,
    DynamicUpload,
    DynamicImageUpload
  },
  data() {
    return {
      // textarray: ['1', '2', '3'],
      // 可拖拽控件列表
      availableComponents: [
        { type: 'DynamicInput', label: '单行文本', required: false, vModel: 'text' },
        { type: 'DynamicDatePicker', label: '日期时间', required: false, vModel: 'datetime' },
        { type: 'DynamicUpload', label: '文件上传', required: false, vModel: 'c' },
        { type: 'DynamicImageUpload', label: '图片上传', required: false, vModel: 'imageupload' },
        {
          type: 'DynamicSelect',
          label: '下拉选择',
          required: false,
          vModel: 'select',
          label: '选项',
          options: ['选项1', '选项2']
        },
        {
          type: 'DynamicCheckboxGroup',
          label: '多选框组',
          required: false,
          vModel: 'checkboxgroup',
          label: '多选',
          options: ['选项A', '选项B', '选项C']
        }
      ],
      // availableComponents: [
      //   { id: 1, label: '张三', type:'1', vModel:'' },
      //   { id: 2, label: '李四', type:'2', vModel:'' }
      // ],
      // 当前设计的表单结构
      formStructure: [
        // { id: 3, label: '王五', type: '3', vModel:''},
      ],
      // 表单数据模型
      formData: {},
      // 当前选中项
      selectedItem: null
    };
  },
  methods: {
    // 控件被添加到设计区时触发
    onComponentAdded(event) {
      // console.log('add:', event);
      const newComponent = this.formStructure[event.newIndex];
      // 动态添加 id 字段
      if (!newComponent.id) {
        newComponent.id = Date.now() + Math.random().toString(36).substring(2);
      }
      this.formData[newComponent.vModel] = '';
      this.selectedItem = newComponent;
      console.log('index:', event.newIndex, 'newComponent:', newComponent, newComponent.id);
      console.log('formStructure:', this.formStructure[event.newIndex]);
    },
    // 点击控件时更新右侧配置
    selectComponent(item) {
      this.selectedItem = item;
    },
    handleComponentClone(component) {
      console.log('component:', component);
      console.log('component.label:', component.label);
      // 创建副本,避免修改原始 availableComponents 中的数据
      const clone = cloneDeep(component);
      clone.id = Date.now() + Math.random().toString(36).substring(2);
      console.log('clone:', clone);
      console.log('clone.id:', clone.id);
      console.log('clone.type:', clone.type);
      console.log('clone.label:', clone.label);
      console.log('clone.vModel:', clone.vModel);
      return clone;
    },
    deepClone(parm) {
      let dataType = Object.prototype.toString.call(parm);
      let result;
      if (dataType === '[object Array]') {
        result = [];
        for (let i = 0; i < parm.length; i++) {
          result[i] = deepClone(parm[i]);
        }
      } else if (dataType === '[object Object]') {
        result = {};
        for (const key in parm) {
          if (Object.hasOwnProperty.call(parm, key)) {
            result[key] = deepClone(parm[key]);
          }
        }
      } else {
        result = parm;
      }
      return result;
    }
  }
};
</script>

<style scoped>
.form-designer {
  display: flex;
  height: 100vh;
}

.component-panel,
.config-panel {
  width: 250px;
  padding: 10px;
  background-color: #f9f9f9;
  border-right: 1px solid #e4e4e4;
}

.design-canvas {
  flex: 1;
  padding: 10px;
  overflow-y: auto;
}

.component-item {
  padding: 8px;
  margin-bottom: 6px;
  background-color: #fff;
  border: 1px solid #ddd;
  cursor: move;
  text-align: center;
}

.form-items {
  min-height: 100px;
  border: 1px dashed #ccc;
  padding: 10px;
}

.form-item {
  border: 1px solid #eee;
  padding: 10px;
  margin-bottom: 10px;
}

.form-item.selected {
  border-color: #409eff; /* Element UI 主色调 */
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.5); /* 可选:添加外发光效果 */
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?

在现代前端开发中&#xff0c;Utility-First (功能优先) CSS 框架已经成为主流。其中&#xff0c;Tailwind CSS 无疑是市场的领导者和标杆。然而&#xff0c;一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …

工厂方法模式和抽象工厂方法模式的battle

1.案例直接上手 在这个案例里面&#xff0c;我们会实现这个普通的工厂方法&#xff0c;并且对比这个普通工厂方法和我们直接创建对象的差别在哪里&#xff0c;为什么需要一个工厂&#xff1a; 下面的这个是我们的这个案例里面涉及到的接口和对应的实现类&#xff1a; 两个发…

鸿蒙Navigation路由导航-基本使用介绍

1. Navigation介绍 Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;Nav…

CMS内容管理系统的设计与实现:多站点模式的实现

在一套内容管理系统中&#xff0c;其实有很多站点&#xff0c;比如企业门户网站&#xff0c;产品手册&#xff0c;知识帮助手册等&#xff0c;因此会需要多个站点&#xff0c;甚至PC、mobile、ipad各有一个站点。 每个站点关联的有站点所在目录及所属的域名。 一、站点表设计…

ZYNQ学习记录FPGA(二)Verilog语言

一、Verilog简介 1.1 HDL&#xff08;Hardware Description language&#xff09; 在解释HDL之前&#xff0c;先来了解一下数字系统设计的流程&#xff1a;逻辑设计 -> 电路实现 -> 系统验证。 逻辑设计又称前端&#xff0c;在这个过程中就需要用到HDL&#xff0c;正文…

Java中HashMap底层原理深度解析:从数据结构到红黑树优化

一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一&#xff0c;是基于哈希表的Map接口非同步实现。它允许使用null键和null值&#xff08;但只能有一个null键&#xff09;&#xff0c;并且不保证映射顺序的恒久不变。与Hashtable相比&#xff0c;Hash…

【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space

问题&#xff1a;IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案&#xff1a;将编译的堆内存增加一点 位置&#xff1a;设置setting-》构建菜单build-》编译器Complier

【阅读笔记】MemOS: 大语言模型内存增强生成操作系统

核心速览 研究背景 ​​研究问题​​&#xff1a;这篇文章要解决的问题是当前大型语言模型&#xff08;LLMs&#xff09;在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色&#xff0c;但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成&#xff08;RA…

【笔记】AI Agent 项目 SUNA 部署 之 Docker 构建记录

#工作记录 构建过程记录 Microsoft Windows [Version 10.0.27871.1000] (c) Microsoft Corporation. All rights reserved.(suna-py3.12) F:\PythonProjects\suna>python setup.py --admin███████╗██╗ ██╗███╗ ██╗ █████╗ ██╔════╝…

五、jmeter脚本参数化

目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …

python基础语法Ⅰ

python基础语法Ⅰ 常量和表达式变量是什么变量的语法1.定义变量使用变量 变量的类型1.整数2.浮点数(小数)3.字符串4.布尔5.其他 动态类型特征注释注释是什么注释的语法1.行注释2.文档字符串 注释的规范 常量和表达式 我们可以把python当作一个计算器&#xff0c;来进行一些算术…

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…

EEG-fNIRS联合成像在跨频率耦合研究中的创新应用

摘要 神经影像技术对医学科学产生了深远的影响&#xff0c;推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下&#xff0c;基于神经血管耦合现象的多模态神经影像方法&#xff0c;通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里&#xff0c;本研…

C++中vector类型的介绍和使用

文章目录 一、vector 类型的简介1.1 基本介绍1.2 常见用法示例1.3 常见成员函数简表 二、vector 数据的插入2.1 push_back() —— 在尾部插入一个元素2.2 emplace_back() —— 在尾部“就地”构造对象2.3 insert() —— 在任意位置插入一个或多个元素2.4 emplace() —— 在任意…

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)

漏洞概述 漏洞名称&#xff1a;Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号&#xff1a;CVE-2023-25194 CVSS评分&#xff1a;8.8 影响版本&#xff1a;Apache Kafka 2.3.0 - 3.3.2 修复版本&#xff1a;≥ 3.4.0 漏洞类型&#xff1a;反序列化导致的远程代…

Copilot for Xcode (iOS的 AI辅助编程)

Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot&#xff0c;它能根据上下文补全代码&#xff0c;快速生成常用…

Axure零基础跟我学:展开与收回

亲爱的小伙伴,如有帮助请订阅专栏!跟着老师每课一练,系统学习Axure交互设计课程! Axure产品经理精品视频课https://edu.csdn.net/course/detail/40420 课程主题:Axure菜单展开与收回 课程视频:

RabbitMQ 各类交换机

为什么要用交换机&#xff1f; 交换机用来路由消息。如果直发队列&#xff0c;这个消息就被处理消失了&#xff0c;那别的队列也需要这个消息怎么办&#xff1f;那就要用到交换机 交换机类型 1&#xff0c;fanout&#xff1a;广播 特点 广播所有消息​​&#xff1a;将消息…

高保真组件库:开关

一:制作关状态 拖入一个矩形作为关闭的底色:44 x 22,填充灰色CCCCCC,圆角23,边框宽度0,文本为”关“,右对齐,边距2,2,6,2,文本颜色白色FFFFFF。 拖拽一个椭圆,尺寸18 x 18,边框为0。3. 全选转为动态面板状态1命名为”关“。 二:制作开状态 复制关状态并命名为”开…