精华内容
下载资源
问答
  • 表单设计工具和报表工具
    千次阅读
    2013-06-18 17:28:03

    收集了一些资料:

    1  、IBM发布开源HTML 5可视化设计工具Maqetta

    4月12日,来自 IBM Impact 2011 的消息,IBM发布 Maqetta,一个创建桌面和移动用户界面的HTML5设计编辑工具,并同时宣布将项目捐助给开源机构Dojo基金会。

     

    2、JotForm - 方便好用的在线Web表单设计工具

     http://www.jotform.com/ 

     

    4、FreeForm是昕友软件开发的免费表单设计器

    智能在线表单设计器 Web Form Builder

    FreeForm : Silverlight Agile Form Engine

     

    http://www.cnblogs.com/FreeForm/

     

    5、智能表单设计工具-可视化表单自定义工具

     

    [详细内容请访问官网:http://www.teamingsoft.com ]

     

    专业的电子表单工具-智能表单工具-自适应解析与展现表单-完善的字段级权限控制-java版本

    关键词:智能表单、电子表单、自适应解析与展现表单、完善的字段级权限控制

    功能特点:

      |__可视化建模   

      |__跨平台运行 -----使用Java语言开发 

      |__支持导出/导入 

      |__支持在线/离线编辑   

      |__支持组织模型导出 -----导出组织模型(各种类型的流程参与者)

      |__支持剪切/复制/粘贴   

      |__拖曳式界面定义   

      |__版本控制 -----用户修改/重定义展现模型是必需 

      |__多数据库支持 -----Oracle / Ms SQLServer / MySQL / DB2 / Sybase  

      |__支持字段级的权限控制 -----每个域都受到权限控制《参见权限审核模型》 

      |__支持三级权限定义 -----每个域都受到:只读 / 编辑 / 不可见 三级权限控制  

      |__支持组织级的权限定义 -----支持“单个用户 / 用户 / 部门 / 岗位 / 角色”五类组织级权限配置 

      |__支持流程级的权限定义 -----支持多流程、多环节的权限配置 

      |__业务展现界面自适应解析 -----业务展现界面与模型相统一,无需再次编码 

      |__支持动态脚本定义 -----丰富用户体验 

      |__支持文档在线编辑/全文批注/手写批注----- office控件实现此功能,更符合用户操作习惯 

      |__支持WAP无线应用的创建 -----内置无线应用创建功能,无需再次编码 

      |__支持智能手机应用的创建 -----内置无线应用创建功能,无需再次编码 

      |__可引用外部应用程序 -----暂支持Java程序的外部调用(CUSTOMAPPLICATION)

     

      注:可以外包。

     

    6、WEB项目中自定义表单的设计与实现

     

     对列表记录的增加,删除,修改操作,

     

    http://cxlh.iteye.com/blog/306952

    http://code.faqee.com/form/list.jsp

     

    7、E8 拖放

     

    8 用Javascript实现鼠标拖拽网页表单

    http://bbs.chinaunix.net/thread-1174666-1-1.html

     

    9

    FineReport报表,用java中Swing来实现,设计器大约在70M左右,支持三层结构的设计模式,不仅形似而且神似excel。应该是中国市场上制表效率最高的报表工具,容易对报表设计器进行二次开发。

     

    10、Oracle WebLogic Integration 自定义控件

    http://www.oracle.com/technetwork/cn/topics/soa/wli-custom-control-spring-component-097790-zhs.html

     

    11、Jasper Report

     

    Jasper Report是用java(最新版也支持Groovy)实现报表的一系列开发包(事实上jasperReport.jar包会依赖于其他几个开源的java包,详细可以在iReport安装目录的/lib目录下找到),最重要的是这一系列工具包大多是开源的,所以在同类产品中算是使用比较广泛。

     

    iReport是基于Jasper Report系列工具包实现的一个图形化报表编辑工具。它通过XML来定义报表模板,可以实现多种数据源的呈现,包括数据库,XML文件,Hibernate,CSV文件等,可以生成PDF,Excel,HTML等报表格式。

     

    由于是比较早的Java开源报表,可以算是Java开源报表工具的鼻祖了,在Java的业界影响比较大,应用也比较多,也有国内的公司在此基础上封装,用作自己的内部报表解决解决方案,例如:清华同方等等很多,在此不一一列举!

     

    12、zkoss

    这是一个封装的ajax框架,其组件支持拖拽。或者java中的canvas也可以实现。

     

    http://www.zkoss.org/zkdemo/userguide/;jsessionid=3494DA92AA2FD0327F78DF37054227F6.zkdemo

     

    13、EMSFLOW VisualWeb 自定义表单

    EMSFLOW VisualWeb 是一个组件化/构件化(Component)J2EE应用快速开发平台,是功能强大的Java/J2EE在线自定义表单工具,包括自定义表单维护、自定义查询、自定义DataGrid等,提供了丰富的页面组件和业务组件,并可在线进行完全可视化JSP页面设计,实现真正的所见即所得(WYSIWYG),可以直接在线定义与运行,也可以生成代码,同时带有增/删/改/查和多表查询与统计与分析的设计模板,做一个多表查询几分钟便可以完全(输入一条SQL语句即可),同时,EMSFLOW3.0提供一套对AJAX(Web2.0)支持的接口,可以轻松完全AJAX应用

     

    http://www.emsflow.com/sample/form-wizard.htm

     

    14、方成的eform自定义表单平台

    eform自定义表单平台是一个在IE浏览器中可视化的设计软件界面的工具。无论是输入界面还是报表界面,无论是简单的输入查询还是复杂的逻辑处理。都可以由eform设计出来。

        eform自定义表单平台适用于网上OA系统的自定义表单模块,工作流系统的自定义表单模块,信息管理系统方面的软件开发项目等等。

    demo:

       http://demo.fcsoft.com.cn:9090/webbill/ebsys/fceform/design/design.htm

    帮助文档:

       http://demo.fcsoft.com.cn:9090/webbill/ebsys/samples/index.htm

     

    大家有更好的开源平台,求推荐,最好是JAVA的!

    参考项目:http://ctfysj.blog.hexun.com/77654121_d.html

    Ext表单设计器实例源码:
    1、ExtJs表单设计器源码
    2、ExtJs OA流程源码
    3、项目数据库(extjs.sql,Mysql的)

    整个项目太大,这里就只上传表单设计器的源码,下载地址:

    华为网盘: http://dl.vmall.com/c0h8vlk9hw

    永硕网盘: http://southpearl.ys168.com/

    Filejungle: http://www.filejungle.com/f/WKw3pJ/formdesigner.rar

    更多相关内容
  • 为您提供f-render可视化表单设计工具下载,f-render 是基于 vue-ele-form 开发的可视化表单设计工具, 适用于 各种流程引擎和动态表单项目,大大节省你的开发时间;注意,此设计器不是独立存在的,是依托于 vue-ele-...
  • vue-ele-form-generator是专为vue-ele-form开发的可视化表单设计工具,并且支持vscode 插件、cli 本地启动、在线设计多种方式,让表单开发的效率更上一层楼! vue-ele-form-generator特性: 1、提供vscode 插件更...
  • 为您提供Arclab Web Form Builder 网页表单设计工具下载,Arclab Web Form Builder(网页表单设计工具)是一款能够帮助前端快速的制作网页的工具,利用工具能够快速的制作出网页表格并进行填充,方便前端快速制作出多...
  • f-render 是基于 vue-ele-form 开发的可视化表单设计工具, 适用于 各种流程引擎和动态表单项目,大大节省你的开发时间;注意,此设计器不是独立存在的,是依托于 vue-ele-form,在使用前请务必阅读 vue-ele-form 的...
  • 为您提供f-render可视化表单设计工具下载,f-render 是基于 vue-ele-form 开发的可视化表单设计工具, 适用于 各种流程引擎和动态表单项目,大大节省你的开发时间;注意,此设计器不是独立存在的,是依托于 vue-ele-...
  • 为您提供vue-ele-form-generator可视化表单设计工具下载,vue-ele-form-generator是专为vue-ele-form开发的可视化表单设计工具,并且支持vscode 插件、cli 本地启动、在线设计多种方式,让表单开发的效率更上一层楼...
  • vue element web 表单设计工具

    万次阅读 2020-12-07 22:22:17
      项目名 dw-form-making,基于 element-ui 组件库的Web端表单设计工具。   项目技术栈vue、vue-cli3,可视化设计element-ui输入框、选择器、单选框等控件组成的Form表单,配置表单字段、标签、校验规则等。  ...

    在这里插入图片描述

    工具概述

    简介

      项目名 dw-form-making,基于 element-ui 组件库的Web端表单设计工具。

      项目技术栈vuevue-cli3,可视化设计element-ui输入框、选择器、单选框等控件组成的Form表单,配置表单字段、标签、校验规则等。

      较早版本采用vuex,由于发布npm包以及项目对vuex依赖性较高(即npm安装后还需配置vuex)等原因,故此种方案抛弃。使用vue.observable实现vuexstatemutations部分。

      项目第三方组件包括vuedraggable拖拽组件、tinymce富文本编辑器、clipboard复制插件、lodash函数库、ace代码编辑器等,其中element-ui未包含在npm发布包内,最大程度减小项目体积,避免二次引入。

      项目样式参考 vue-form-making 基础版本,表单组件未采用v-if判断方式渲染,原因一是表单组件较多,几乎全是v-if,容易造成代码冗余阅读性差,二是栅格布局采用组件递归,此种方式页面渲染性能差,每次递归页面v-if重复数次,故抛弃此种方式,采用动态组件方式渲染表单,不仅可读性高性能也好。

      由于经常使用vue-form-making,而后对其实现方式较为感兴趣,故在参考原样式基础上,项目js部分完全脱离vue-form-making方式,从零开始重构vue-form-making基础版本代码。

      项目可熟练巩固使用element-ui表单组件和部分Dialog对话框、Message消息提示、Container布局容器等。涉及递归组件内作用域插槽、组件循环引用处理、Git多远程库维护、npm包发布。

    项目预览

      GitHub / Gitee

    示意图

    在这里插入图片描述

    文件目录配置

    ├── dist
    ├── docs
    ├── lib
    ├── public
    ├── src
    │   ├── assets
    │   │   ├── fonts
    │   │   ├── images
    │   │   ├── js
    │   ├── components
    │   │   ├── ButtonView
    │   │   │   ├── GenerateForm.vue
    │   │   │   ├── ViewForm.vue
    │   │   │   ├── Widget.vue
    │   │   ├── ConfigOption
    │   │   │   ├── FieldProperty.vue
    │   │   │   ├── FormProperty.vue
    │   │   ├── AceEditor.vue
    │   │   ├── PublicDialog.vue
    │   ├── elements
    │   │   ├── input
    │   │   │   ├── config.vue
    │   │   │   ├── view.vue
    │   │   ├── radio
    │   │   │   ├── config.vue
    │   │   │   ├── view.vue
    │   │   ├── ...
    │   │   ├── CommonField.vue
    │   │   ├── CommonView.vue
    │   │   ├── config.js
    │   │   ├── index.js
    │   │   ├── view.js
    │   ├── layout
    │   │   ├── index.vue
    │   │   ├── components
    │   │   │   ├── ButtonView.vue
    │   │   │   ├── ConfigOption.vue
    │   │   │   ├── ElementCate.vue
    │   │   │   ├── LinkHeader.vue
    │   ├── store
    │   │   ├── index.js
    │   │   ├── vuex.js
    │   ├── styles
    │   │   ├── index.scss
    │   │   ├── layout.scss
    │   ├── utils
    │   │   ├── index.js
    │   │   ├── format.js
    │   │   ├── vue-component.js
    │   ├── App.vue
    │   ├── main.js
    │   ├── index.js
    │   ├── package.json
    │   ├── README.md
    │   ├── vue.config.js
    

    初始化

    脚手架初始化

      初始空脚手架vue-cli3仅配置BabelCSS Pre-processorsscss),删除其余业务不相关部分,文件夹部分根据需求逐步创建。

      项目核心组件库element-ui,由于整个项目完全依赖element-ui,所以可以直接全局引入。但是npm包发布不引入,最大程度减小项目体积,具体后续还会提到。

    npm i element-ui -S
    
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    Vue.use(ElementUI)
    

      其次项目核心拖拽业务组件 vuedraggable,拖拽页面部分引入即可,不用全局引入。

    npm i -S vuedraggable
    
    import draggable from 'vuedraggable'
    ...
    export default {
      components: {
        draggable,
      },
    }
    ...
    

      初始化样式使用 normalize.css,项目定制样式初始化stylesindex.scss,其余布局相关、组件相关样式统一放在layout.scss

    页面布局

      其中ButtonView视图区域components维护组件GenerateForm.vueViewForm.vueWidget.vueConfigOption配置参数维护组件FieldProperty.vue字段属性、FormProperty.vue表单属性。

    在这里插入图片描述

      项目基本布局确定完毕,开始实现具体结构。创建layout文件夹,维护整个页面布局相关部分,App.vue中只做layout的引入,这样后期App.vue基本不作改动,同时最为关键的是,最终发布为npm包时,整个layout注册为组件,方便引入。

    // App.vue
    <div id="app">
      <Layout />
    </div>
    
    import Layout from './layout/index'
    ...
    export default {
      name: 'App',
      components: {
        Layout,
      },
    }
    ...
    
    // index.js
    import MakingForm from './layout/index'
    ...
    export {
      ...
      MakingForm
    }
    

      layout index.vue作为组件导出,其中layout内部使用element-ui container布局容器,四个页面主要区域放在同级components文件夹下,底部Powered by代码较少,不用再作抽离。四个主要区域设置类名,ElementCate固定宽度250pxConfigOption固定宽度300pxButtonView最小宽度440px,防止屏幕宽度较小样式错乱。

    <el-container class="dw-form-making-container">
      <el-header class="dw-form-making-header">
        <link-header />
      </el-header>
    
      <el-container class="dw-form-making-body">
        <el-aside class="dw-form-making-elementcate" width="250px">
          <element-cate />
        </el-aside>
    
        <el-main class="dw-form-making-buttonview">
          <button-view />
        </el-main>
    
        <el-aside class="dw-form-making-configoption" width="300px">
          <config-option />
        </el-aside>
      </el-container>
    
      <el-footer class="dw-form-making-footer">...</el-footer>
    </el-container>
    

      ElementCate部分首先考虑各个元素数据和图标,暂不考虑元素的其他情况(配置信息等),iconfont 创建个人项目,选择合适的图标,下载本地压缩包解压导入,注意iconfont.css导入路径前加上~符号,从vue.config.jsalias查询相关路径加载模块,不添加~默认为当前目录下路径。

    @import '~assets/fonts/iconfont.css';
    

      ElementCate.vue中引入三个不同类别的表单组件,假设某个js文件(elements文件夹内index.js)对外导出三个数组,分别为basicadvancelayout,且每个数组对象暂时包含name标签、icon图标。

    // element -> index.js
    const basic = [
      ...
      {
        name: '单行文本',
        icon: 'icon-input'
      }
      ...
    ]
    
    const advance = []
    
    const layout = []
    
    export {
      basic,
      advance,
      layout,
    }
    

      ElementCate.vue引入三个数组,暂时使用ul li渲染出来,li设置为块级再指定宽度48%,其中图标和组件名均对齐中线,同时设置表单组件悬浮的样式。

      ButtonView按钮视图区域分为上下两部分,按钮区域和视图区域,按钮区域暂时放置对应按钮,事件后续接入逻辑详细处理,视图区域抽离为组件ViewForm.vue,现在暂时放置一个div盒子。

      ConfigOption配置参数区域分为字段属性、表单属性,实现最基本的Tabs切换即可。

    vuedraggable 拖动与 transition-group

      vuedraggable 官方文档提供了vuedraggabletransition-group配合使用的示例方法,这里详细说明项目元素分类和视图表单区域的配置参数。

    • tag: draggable渲染后的标签名
    • value: 和内部元素v-for指令引用相同的数组,不应该直接使用,可通过v-model
    • group.name: 同分组名可相互拖动,不同draggable列表也可以
    • group.pull: 拖动至其他分组克隆或复制,而非直接取出再移动
    • group.put: 其他组别拖动至当前分组是否放入
    • sort: 同分组拖动后不排序
    • animation: 单位ms,与transition-group产生过渡效果
    • ghostClass: 被拖动元素class类名
    • handle: 拖动列表元素上指定类名部分(拖动小图标)才能进行拖动
    • clone: 克隆事件,声明使用:,处理克隆后的元素
    • add: 添加事件,其他分组拖动至当前分组,处理添加前的元素
    // ElementCate.vue
    <draggable
      v-model="list"
      tag="ul"
      v-bind="{
        group: {
          name: 'view',
          pull: 'clone',
          put: false,
        },
        sort: false,
      }"
      :clone="handleClone"
    >
      <li>...</li>
      ...
    </draggable>
    
    // ViewForm.vue
    <draggable
      v-model="list"
      v-bind="{
        group: 'view',
        animation: 200,
        ghostClass: 'move',
        handle: '.drag-icon',
      }"
      @add="handleAdd"
    >
      <transition-group>...</transition-group>
    </draggable>
    

      ElementCate部分根据上述配置,引入分类列表basicadvancelayout,注册Draggable组件,其中分类列表长度若为0,对应列表标题也不显示,不用外层添加DOM元素,使用template配合v-if使用。clone函数拖动时触发,参数为拷贝的元素对象,暂时打印,返回拷贝的对象。

      视图表单部分使用absolute绝对定位使高度为整个下半部分区域,draggable覆盖区域高度不够不会产生拖动且内部绑定的list暂时为data内变量。add函数参数解构newIndex(列表内索引),通过索引可获取拖入后的元素。控制台查看视图表单内列表,当元素拖入(鼠标不松开),元素类名为element-cate-item move,鼠标松开渲染为视图表单列表元素,layout.scss设置拖入样式。由于视图表单也存在元素的拖动情况,故样式声明为变量,使用时引入。

    @mixin form-item-move {
      outline-width: 0;
      height: 3px;
      overflow: hidden;
      border: 2px solid #409eff;
      ...
    }
    
    .element-cate-item {
      &.move {
        @include form-item-move;
      }
      ...
    }
    

      FormProperty可配置按钮视图中对齐方式、宽度、组件尺寸等,故将按钮视图中draggable放入el-form组件内,每一个列表元素渲染为el-form-itemel-form配置固定,el-form-item暂时渲染label和输入框。注意transition-group内部元素必须设置key值,否则元素无法渲染并且控制台会打印警告。

    <el-form size="small" label-width="100px" label-position="right">
      <draggable ... @add="handleAdd">
        <transition-group>
          <div v-for="(element, index) in data" :key="index" class="view-form-item">
            <el-form-item :label="element.name">
              <el-input />
            </el-form-item>
          </div>
        </transition-group>
      </draggable>
    </el-form>
    
    handleAdd({ newIndex }) {
      this.select = this.data[newIndex]
    }
    

      ElementCate元素拖入ViewForm可以看见蓝色长条,鼠标松开渲染为输入框和标签,设置view-form-item样式和hover样式,边框色同ElementCate元素一致。当点击view-form-item时,data中变量select保存点击的view-form-item,判断显示出蓝色边框和拖动图标。

    <div :class="['view-form-item', { active: select.key === element.key }]" @click="handleSelect(element)">
      <el-form-item ...>...</el-form-item>
      ...
      <div v-if="select.key === element.key" class="item-drag">
        <i class="iconfont icon-drag drag-icon"></i>
      </div>
      ...
    </div>
    
    handleSelect(element) {
      this.select = element
    }
    

      首先要明确的是,分类元素中clone事件返回的对象就是视图表单放入的对象,故可以在clone回调时添加key属性或者表单视图add事件内newIndex获取元素添加key属性。但是两种方式有明显差异,前者鼠标拖动clone返回对象并添加key值,鼠标松开add活动元素select设为当前元素(拖入的元素高亮),后者鼠标拖动clone返回对象,鼠标松开add添加key后再设置活动元素。虽然实现效果并无差异,但是后者一个函数做了两件事(添加key、高亮),不符合单一职责原则SRP

      key值使用4位随机字符串和时间戳方式。clone函数参数为拖动元素引用,故返回对象时要另拷贝,对象拷贝使用 lodash.deepClone,也可以使用JSON深拷贝,但是JSON.stringify序列化时会丢失掉函数等类型,不推荐使用。utils工具类下暴露出uuiddeepClone

    // ElementCate.vue
    handleClone(element) {
      return Object.assign(deepClone(element), { key: uuid() })
    }
    
    // utils -> index.js
    import lodash from 'lodash'
    
    function deepClone(object) {
      return lodash.cloneDeep(object)
    }
    
    function S4() {
      return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
    }
    
    function uuid() {
      return Date.now() + '_' + S4()
    }
    
    export {
      uuid,
      deepClone
      ...
    }
    

    Elements 元素参数和 vuex

      上述部分已基本实现元素拖动和单击高亮,但是view-form-item还渲染为输入框,若ElementCate元素有配置参数,可根据不同配置渲染不同表单元素,暂时采用v-if方式。

    // elements -> index.js
    const basic = [
      {
        name: '单行文本',
        icon: 'icon-input',
        type: 'input'
      },
      {
        name: '多行文本',
        icon: 'icon-textarea',
        type: 'textarea'
      }
      ...
    ]
    
    // ViewForm.vue
    <el-form-item :label='element.name'>
      <el-input v-if='element.type === "input"' />
    
      <el-input type='textarea' v-if='element.type === "textarea"' />
      ...
    </el-form-item>
    

      ElementCate元素拖入,高亮同时字段属性能配置不同参数,但是字段属性与视图表单没有关联,vue-form-making基础版本内部采用组件传值,活动元素select传递到顶层layout再发送至FieldProperty.vue,首先组件层级较深且代码可读性差,优化组件层级,组件树结构又不合理,很难兼备。若存在全局状态管理,解决方式就很灵活,同时也不影响组件层级和结构。

      vuex的确能很好地解决上述问题,但是项目对vuex依赖性不高并且项目不大,仅仅使用state状态管理显得多余。而vue.observable方式不仅可以实现部分vuex功能,项目也会显得轻量。视图表单内select活动元素state下维护,视图表单内computed引入,元素拖入和单击时调用mutations设置活动元素。FieldProperty.vue同理引入select,暂时可配置元素标签名。

    // store -> index.js
    export default new Vuex.Store({
      state: {
        select: {}
      },
      mutations: {
        SET_SELECT(state, select) {
          if (state.select === select) return
          state.select = select
        }
      }
    }
    
    // ViewForm.vue
    import store from 'store/index.js'
    
    export default {
      ...
      computed: {
        select() {
          return store.state.select
        },
      },
      methods: {
        handleSelect(element) {
          store.commit('SET_SELECT', element)
        },
    
        handleAdd({ newIndex }) {
          store.commit('SET_SELECT', this.data.list[newIndex])
        },
      },
    }
    
    // FieldProperty.vue
    <el-form size="small" label-position="top">
      <el-form-item label="标签">
        <el-input v-model="data.name"></el-input>
      </el-form-item>
    </el-form>
    
    export default {
      ...
      computed: {
        data() {
          return store.state.select
        }
      }
    }
    

      若单行文本含placeholder,多行文本不含placeholderFieldProperty.vue内渲染配置项就会不一样,也采用v-if方式。placeholdername标签不同,属于元素具体配置,放在options下。

    // elements -> index.js
    const basic = [
      {
        name: '单行文本',
        icon: 'icon-input',
        type: 'input',
        options:{
          placeholder:''
        }
      },
      {
        name: '多行文本',
        icon: 'icon-textarea',
        type: 'textarea'
      }
      ...
    ]
    
    // ViewForm.vue
    <el-form-item :label="element.name">
      <el-input v-if="element.type === 'input'" :placeholder="element.options.placeholder" />
    
      <el-input v-if="element.type === 'textarea'" type="textarea" />
      ...
    </el-form-item>
    
    // FieldProperty.vue
    <el-form size="small" label-position="top">
      <el-form-item label="标签">
        <el-input v-model="data.name"></el-input>
      </el-form-item>
    
      <el-form-item v-if="data.type === 'input'" label="占位内容">
        <el-input v-model="data.options.placeholder"></el-input>
      </el-form-item>
    </el-form>
    

    表单元素操作

    全局表单配置

      其实表单也是一个全局变量,包含表单配置(对齐方式、宽度、组件尺寸等)和内部元素。ViewForm.vue内部data维护至store,对表单和活动元素的操作,基本都在mutations内部。

    // store -> index.js
    export default new Vuex.Store({
      state: {
        select: {},
        data: {
          list: [],
          config: {
            labelWidth: 100,
            labelPosition: 'right',
            size: 'small',
            customClass: '',
          },
        },
      },
    })
    
    // ViewForm.vue
    <el-form
      :size="data.config.size"
      :label-width="data.config.labelWidth + 'px'"
      :label-position="data.config.labelPosition"
    >
      ...
    </el-form>
    
    export default {
      computed: {
        data() {
          return store.state.data
        },
      },
    }
    
    // FormProperty.vue
    <el-form label-position="top" size="small">
      <el-form-item label="标签对齐方式">
        <el-radio-group v-model="data.labelPosition"> ... </el-radio-group>
      </el-form-item>
    </el-form>
    
    export default {
      ...
      computed: {
        data() {
          return store.state.data.config
        },
      },
    }
    

    动态组件

      目前元素可拖动至视图表单,同时配置标签等,表单也可全局配置。但是按钮视图的元素还很单一,逐渐完善后数量多达20个左右,若输入框等组件仅仅通过v-if判断渲染,首先全篇几乎是v-if全等判断,阅读性非常差,其次每渲染一个组件就会经过20次的v-if,视图表单引入栅格后,栅格每嵌套一级,v-if重复20次,表单一旦栅格层级较深、元素较多,渲染性能会非常差,再者后期自定义添加表单组件,每添加一个组件,调整代码的地方会非常多,维护非常困难。参考 vue-form-making 基础版本,高级版可能已重构,而且性能很好。表单配置也同理,全篇v-if不是最终解决办法,动态组件将会很好解决这个问题。

      ElementCate每一个元素都对应一个表单组件、表单配置组件,根据ElementCate的组件名动态渲染,代码量会大大精简,只是视图表单初始化、字段属性初始化需要引入多个组件,需要用到require.context自动化导入模块,避免重复代码和手动导入。

      elements放置表单组件,ElementCate若添加组件,element下新增组件即可,不用去考虑视图表单内部的渲染。index.js配置ElementCate元素,view.jsconfig.js自动化导入config.vue并注册组件。
      添加单行文本组件,elements下新建input,创建config.vueview.vue,必须配置组件名。

    // elements 组件目录
    │   ├── elements
    │   │   ├── input
    │   │   │   ├── config.vue
    │   │   │   ├── view.vue
    │   │   ├── ...
    │   │   ├── config.js
    │   │   ├── index.js
    │   │   ├── view.js
    
    // index.js
    const basic = [
      {
        name: '单行文本',
        icon: 'icon-input',
        type: 'input',
        component: 'DwInput',
        options: {
          placeholder: '',
        },
      },
    ]
    
    // view.vue
    <el-form-item ...>
      <el-input :placeholder="element.options.placeholder" ...></el-input>
    </el-form-item>
    
    export default {
      name: 'DwInput',
      props: {
        element: {
          type: Object,
        },
      },
      ...
    }
    
    // config.vue
    <el-form size="small" label-position="top">
      <el-form-item label="标签">
        <el-input v-model="data.name"></el-input>
      </el-form-item>
    </el-form>
    
    export default {
      name: 'DwInputConfig',
      ...
    }
    

      view.js动态导入elements下所有view.vue文件,对外导出为组件列表,config.js同理。

    // view.js
    const components = {}
    const requireComponent = require.context('elements/', true, /(view.vue)$/)
    
    requireComponent.keys().forEach(fileName => {
      const componentOptions = requireComponent(fileName)
      const component = componentOptions.default || componentOptions
    
      components[component.name] = component
    })
    
    export default components
    
    // ViewForm.vue
    <div v-for="element in data.list" class="view-form-item">
      <component :is='item.component' :element='element'>
    
      <div v-if="select.key === element.key" class="item-drag">
        <i class="iconfont icon-drag drag-icon"></i>
      </div>
    </div>
    
    import Draggable from 'vuedraggable'
    import components from 'elements/view'
    
    export default {
      ...
      components: {
        Draggable,
        ...components,
      },
    }
    
    // FormProperty.vue
    <component :is="component.component && `${component.component}Config`" :data="component" />
    
    import components from 'elements/config'
    
    export default {
      components,
      computed: {
        component() {
          return store.state.select
        },
      },
    }
    

    公共字段属性和公共视图

      通用字段属性包括字段标识model、标签name、标签宽度(isLabelWidthlabelWidth)、隐藏标签hideLabel、自定义Class customClass五个属性,字段标识即字段名,默认生成的字段标识为元素typekey值,字段标识modelkey值一起生成。

    // ElementCate
    handleClone(element) {
      const key = uuid()
    
      return Object.assign(deepClone(element), {
        key,
        model: element.type + '_' + key,
      })
    },
    

      五个属性封装为公共组件CommonField.vue,放置插槽,组件config.vue引用,组件独有配置插入插槽即可。要注意的是,组件传值是单向的,但是CommonField.vue内部却能修改传入的值,原因是组件传引用类型的值实际传递的是引用地址,所以组件内部修改外部依然同步。组件传值不仅可以使用sync实现双向传值,也可传递引用类型实现组件双向传值。

    <common-field :data="data">
      <template slot="custom"> ... </template>
    </common-field>
    
    import CommonField from '../CommonField'
    
    export default {
      components: {
        CommonField,
      },
    }
    

      公共组件CommonField.vueCommonField.vue同步,el-form-item插槽labellabel-width显隐标签,el-form-item添加class自定义customClassisLabelWidth控制标签宽度,标签不显示,宽度为0,标签显示且自定义宽度,宽度为自定义值,标签显示但不自定义宽度,宽度为表单标签宽度。

    <el-form-item
      :label-width="
        element.options.hideLabel
          ? '0px'
          : (element.options.isLabelWidth ? element.options.labelWidth : config.labelWidth) + 'px'
      "
      :class="element.options.customClass"
    >
      <template v-if="!element.options.hideLabel" slot="label">
        {{ element.name }}
      </template>
    
      <slot></slot>
    </el-form-item>
    

    元素拷贝、删除和伪元素

      ViewForm内元素的字段标识model可创建div盒子定位或者运用css伪元素实现。

      view-form-item自定义属性data-modelcss伪元素content内部attr函数获取。ViewForm表单元素禁止输入,同理可定位div盒子或者css伪元素,伪元素绝对定位四个参数都设为0且父元素相对定位,可实现宽高等于父元素。

    // ViewForm.vue
    <div v-for="element in data.list" class="view-form-item" :data-model="element.model">...</div>
    
    // layout.scss
    .view-form-item {
      position: relative;
      ...
      &::before {
        content: attr(data-model);
        position: absolute;
        top: 3px;
        right: 3px;
        font-size: 12px;
        color: rgb(2, 171, 181);
        z-index: 5;
        font-weight: 500;
      }
    
      &::after {
        position: absolute;
        content: '';
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        z-index: 5;
      }
    }
    

      view-form-item内部添加克隆图标,传递参数包括克隆元素、索引值、列表元素,处理函数在store中维护,拷贝元素key值和model重新生成,克隆后活动元素select重置。

    // ViewForm.vue
    <div v-for="(element, index) in data.list" class="view-form-item">
      ...
      <i class="iconfont icon-clone" @click="handleClone(element, index, data.list)" />
    </div>
    
    handleClone(element, index, list) {
      store.commit('CLONE_ELEMENT', { index, element, list })
    },
    
    // store -> index.js
    CLONE_ELEMENT(state, { index, element, list }) {
      const key = uuid()
      const el = deepClone(element)
    
      list.splice(
        index + 1,
        0,
        Object.assign(el, {
          key,
          model: element.type + '_' + key,
        })
      )
    
      state.select = list[index + 1]
    },
    

      删除按钮为避免重复点击,只在第一次点击时触发,元素删除动画触发过程中不可再点击。元素删除前更新活动元素select,被删除元素处在列表末尾且长度大于1,活动元素为上一个元素。若处在列表末尾且长度等于1,即列表只有一个元素,活动元素为空。不满足上述则元素处在中部,删除后活动元素为下一个元素。

    // ViewForm.vue
    <div class="view-form-item">
      ...
      <i class="iconfont icon-trash" @click.once="handleDelete(data.list, index)" />
    </div>
    
    handleDelete(list, index) {
      store.commit('DELETE_ELEMENT', { list, index })
    },
    
    // store -> index.js
    DELETE_ELEMENT(state, { list, index }) {
      if (list.length - 1 === index) {
        state.select = index ? list[index - 1] : {}
      } else {
        state.select = list[index + 1]
      }
    
      list.splice(index, 1)
    },
    

    ElementCate 组件

      组件参数引用CommonFild.vue公共字段属性,默认含有5个公共属性,宽度、操作属性、校验规则等根据实际情况加入,定制化属性添加至组件,再由插槽插入内部。组件视图引用CommonView.vue公共视图,负责表单活动样式、标签、字段属性等,组件引用后不考虑表单呈现,仅专注同步组件参数部分。

      组件视图部分view.vue,由于表单预览可获取表单内部值,显然组件实现v-model双向绑定,组件内部暂时接收传值value,预览部分再自定义组件v-model

      下面简述组件定制化部分,诸如placeholer占位内容、style宽度等参考源代码。

    单行文本

      单行文本参数包括宽度、默认值、占位内容、操作属性等,校验规则较为复杂,暂不考虑。新增组件参数和视图部分均可参考单行文本源码,单行文本禁用和只读属性二者择其一,不能同时作用于同一表单元素。

    // elements -> input -> config.vue
    <template slot="option">
      <el-checkbox v-model="data.options.disabled" :disabled="data.options.readonly">禁用</el-checkbox>
      <el-checkbox v-model="data.options.readonly" :disabled="data.options.disabled">只读</el-checkbox>
      ...
    </template>
    

    多行文本

      多行文本参数部分,默认值使用文本域。

    // elements -> textarea -> config.vue
    <el-form-item label="默认值">
      <el-input type="textarea" ... />
    </el-form-item>
    

    计数器

      计数器操作按钮位置传递参数controls-position,默认为default,默认值受最大值、最小值、步数限制。

    // elements -> number -> view.vue
    <common-view>
      <el-input-number
        :value="element.options.defaultValue"
        :controls-position="element.options.controlsPosition"
      />
    </common-view>
    
    // elements -> number -> config.vue
    <el-form-item label="默认值">
      <el-input-number
        v-model="data.options.defaultValue"
        :max="data.options.max"
        :min="data.options.min"
        :step="data.options.step"
      />
    </el-form-item>
    

    单选框组

      单选框组布局方式分为块级和行内,选项包括静态数据和动态数据,暂不考虑动态数据,选项为label-value对形式,内部引用draggable拖动列表,选项可删除和新增,添加选项生成随机label-value对,选中选项设置默认值,清空列表时默认值清空,选中项删除,清空默认值。注意el-radio组件,若不显示label,可传入nbsp空位符。

    // elements -> radio -> view.vue
    <el-radio
      v-for="(item, index) in element.options.options"
      style="{
        display: element.options.inline
        ? 'inline-block' : 'block'
      }"
      >{{ item.label }}</el-radio>
    
    // elements -> radio -> config.vue
    <li ...>
      <el-radio :label="item.value" class="hidden-label">&nbsp;</el-radio>
      ...
    </li>
    

    多选框组

      多选框组与单选框组大同小异,多选框组默认值为数组,多选框组默认值可选择多个,删除选中项时,首先获取选中项value值在默认值中的索引,满足则删除默认值对应项,不满足只删除选中项。

    // elements -> checkbox -> config.vue
    handleDeleteOptions(element, index) {
      const i = this.data.options.defaultValue.indexOf(element.value)
    
      if (i > -1) {
        this.data.options.defaultValue.splice(i, 1)
      }
    
      this.data.options.options.splice(index, 1)
    },
    

    时间选择器

      时间选择器默认值受格式控制,也包括禁用和只读,同单行文本一致,只能二者选其一。时间选择为占位内容,范围选择包括开始占位内容、范围分隔符、结束占位内容。开启范围选择时默认值只能为null,关闭时设为空字符。el-time-picker组件v-bind绑定is-range,范围选择切换导致选择器定位错乱,是element组件自身的bugv-if与范围选择参数一致并且指定key值可解决,两者缺一不可。

    // elements -> time -> config.vue
    <el-form-item label="默认值">
      <el-time-picker v-if="data.options.isRange" key="range" is-range ... />
      <el-time-picker v-else key="default" ... />
    </el-form-item>
    
    handleRangeChange() {
      this.data.options.defaultValue = this.data.options.isRange ? null : ''
    },
    

    日期选择器

      日期选择器显示类型包括月份、年份、日期、多日期、日期范围等,范围类型默认值为null,其余为空字符,格式对应,切换类型选择器错乱处理方式与时间选择器一致。

    // elements -> date -> config.vue
    export default {
      data() {
        return {
          type: [
            {
              label: '日期时间范围',
              value: 'datetimerange',
              format: 'yyyy-MM-dd HH:mm:ss',
              type: null,
              isRange: true,
            },
          ],
        }
      },
      methods: {
        handleTypeChange(value) {
          const showType = this.type.find(e => e.value === value)
    
          this.data.options.format = showType.format
          this.data.options.defaultValue = showType.type
          ...
        },
      },
    }
    

    评分

      评分默认值受半选、最大值控制,最大值最小为1,默认值清空为0

    // elements -> rate -> config.vue
    <el-rate ... :allow-half="data.options.isAllowhalf" :max="data.options.max" />
    

    颜色选择器

      颜色选择器选择颜色后,元素默认值为hex十六进制,勾选透明度,点击颜色选择器,默认值颜色并未改变,是el-color-picker组件自身的bug,解决方式类似时间选择器,v-ifkey值两者共同作用。

    // elements -> color -> config.vue
    <el-form-item label="默认值">
      <el-color-picker v-if="data.options.showAlpha" key="alpha" ... show-alpha />
      <el-color-picker v-else key="default" ... />
    </el-form-item>
    

    下拉选择器

      下拉选择器添加选项与单选框组一致,删除元素即单选框组和多选框组的合并,单选多选切换保留默认值方式有差异。单选过渡多选,单选未选择默认值,值为空数组,单选选择默认值,值为包含默认值的数组。多选过渡单选,多选未选择默认值,值为null,多选选择默认值,值为数组首个元素值。

    // elements -> select -> config.vue
    handleMultipleChange(multiple) {
      var value = this.data.options.defaultValue
    
      this.data.options.defaultValue = multiple
        ? value === null
          ? []
          : [value]
        : value.length
        ? value[0]
        : null
    },
    

    开关

      开关参考el-switch参数,可自定义开启和关闭的文字颜色、文字描述。

    // elements -> switch -> view.vue
    <el-switch
      :active-color="element.options.isColor ? element.options.activeColor : '#409EFF'"
      :inactive-color="element.options.isColor ? element.options.inactiveColor : '#C0CCDA'"
      :active-text="element.options.isText ? element.options.activeText : ''"
      :inactive-text="element.options.isText ? element.options.inactiveText : ''"
    />
    

    滑块

      滑块默认值受最大值、最小值、步长限制。

    // elements -> slider -> config.vue
    <el-slider
      v-model="data.options.defaultValue"
      :max="data.options.max"
      :min="data.options.min"
      :step="data.options.step"
    />
    

    文字

      文字仅是一小段段落,丰富组件列表和部分表单的描述信息,由于可指定宽度,则元素为块级元素。

    // elements -> text -> view.vue
    <div :style="{ width: element.options.width }">
      <span style="word-break: break-all">{{ value }}</span>
    </div>
    

    html

      html组件默认值暂时为文本域,可填写html代码即可,视图部分利用v-html指令。

    // elements -> html -> view.vue
    <div :style="{ width: element.options.width }">
      <div v-html="value" />
    </div>
    

    级联选择器

      级联选择器一般异步获取数据源,默认含labelvaluechildren字段,也可指定属性配置,可选项数据源options暂时为空数组。

    // elements -> cascader -> view.vue
    <el-cascader
      :props="{
        value: element.options.props.value,
        label: element.options.props.label,
        children: element.options.props.children,
      }"
      :options="[]"
      ...
    />
    

    分割线

      分割线content-position控制文本位置。

    // elements -> divider -> view.vue
    <el-divider :content-position="element.options.textPosition">
      {{ element.name }}
    </el-divider>
    

    栅格布局

      上述部分仅仅支持单行单表单组件,尚无法满足简单的栅格布局,即一行无法显示多个表单组件,vue-form-making基础版本不支持栅格布局,但是其样式和参数可作为参考。

      栅格样式不同于其他组件,view-form-item判断是否为栅格元素,动态生成类名。栅格样式权重应高于普通样式,栅格样式代码顺序在普通样式后层叠。

    // ViewForm.vue
    <div
      :class="[
        'view-form-item',
        {
          active: select.key === element.key,
          grid: element.type === 'grid',
        },
      ]"
      ...
    >
      ...
    </div>
    
    // layout.scss
    .view-form-item {
      ...
    }
    
    .view-form-item.grid {
      ...
    }
    

      栅格参数暂不考虑,一行显示两列。对比ViewForm.vueElementCate内元素若能拖入栅格内,首先栅格内渲染的列表要绑定draggable,即draggleble包含栅格列表,其次draggable覆盖区域必须足够高,否则元素拖不进来。栅格内暂时渲染元素对象,v-model绑定data内变量,元素拖入后可以观察到数据已拖入并渲染。

    // elements -> grid -> view.vue
    <el-row type="flex">
      <el-col :span="12">
        <draggable
          v-model="list"
          v-bind="{
            group: 'view',
          }"
        >
          <transition-group tag="div" class="el-col-list">
            <div v-for="(element, index) in list" :key="index">
              <span>{{ element }}</span>
            </div>
          </transition-group>
        </draggable>
      </el-col>
      <el-col :span="12">
        <div class="el-col-list"></div>
      </el-col>
    </el-row>
    
    ...
    export default {
      data() {
        ...
        return {
          list: [],
        }
      },
    }
    

      el-col-list内元素渲染为表单组件,局部批量注册组件。

    // elements -> grid -> view.vue
    <transition-group tag="div" class="el-col-list">
      <component :is="element.component" v-for="(element, index) in list" :key="index" :element="element" />
    </transition-group>
    
    import Draggable from 'vuedraggable'
    import components from 'elements/view'
    
    export default {
      ...
      name: 'DwGrid',
      components: {
        Draggable,
        ...components,
      },
      data() {
        return {
          list: [],
        }
      },
    }
    

      ElementCate元素拖入,控制台会报错组件未注册,但是代码内明确注册了组件。在生命周期beforeCreate内打印this.$options.components,页面注册的组件只有Draggable和栅格DwGrid。其余批量注册的组件均不存在,即组件并未注册。造成错误的原因是组件之间的循环引用,若表单元素全局注册,这种错误不会存在。但是组件局部注册,DwGrid内部引用DwGrid,就变成了一个循环,组件不知道如何完全解析出自身。解决方式有两种,vue 官方给出了示例,由于是批量注册,webpack的异步import不适用,在生命周期beforeCreate时去注册它。

    import components from 'elements/view'
    
    export default {
      ...
      beforeCreate() {
        Object.assign(this.$options.components, components)
      },
    }
    

      此时ElementCate元素拖入,对应表单可渲染,参考ViewForm.vue内部view-form-list,配置draggable参数,部分克隆、删除事件暂不考虑,函数设为空函数。

    // elements -> grid -> view.vue
    <transition-group class="el-col-list" ...>
      <div
        v-for="element in list"
        :key="element.key"
        :class="[
          'view-form-item',
          {
            active: select.key === element.key,
            grid: element.type === 'grid',
          },
        ]"
        :data-model="element.model"
        @click.stop="handleSelect(element)"
      >
        <component :is="element.component" :element="element" />
    
        <div v-if="select.key === element.key" class="item-drag">
          <i class="iconfont icon-drag drag-icon"></i>
        </div>
    
        <div v-if="select.key === element.key" class="item-action">
          <i class="iconfont icon-clone" @click.stop="handleClone"></i>
          <i class="iconfont icon-trash" @click.stop.once="handleDelete"></i>
        </div>
      </div>
    </transition-group>
    
    export default {
      methods: {
        handleSelect(element) {
          store.commit('SET_SELECT', element)
        },
    
        handleClone() { },
    
        handleDelete() { },
      },
    }
    

      细致发现,ViewForm.vue和栅格内部view-form-item代码完全一致(逻辑部分暂不考虑),一般抽离公共代码,封装成一个组件,但是可以梳理页面结构并最终发现,代码一致是必然的。首先ViewForm.vue是一个单一的列表,组件拖入并渲染单个元素,引入栅格后,每个栅格代表一个列表,栅格列表与ViewForm.vue的列表实质是同一种列表,拖入的组件也是同一类组件,所以最终列表(view-formel-col-list)内代码是一致的。

      公共部分代码为Widget.vue小部件,即对每个组件的一层包装,包括点击高亮、拖动、克隆、删除事件,组件传值暂时为elementindex(元素索引)。

    // ViewFrom.vue
    <transition-group class="view-form">
      <widget v-for="(element, index) in data.list" :key="element.key" :index="index" :element="element" />
    </transition-group>
    
    import Widget from './Widget'
    
    export default {
      components: {
        Widget,
      },
    }
    

      栅格组件内部引入小部件,拖入ElementCate元素,页面报错组件渲染失败。根据报错信息很难排查问题原因,细致梳理页面结构,小部件批量引入表单组件,其中包含输入框、栅格等,栅格内部引入小部件,又是组件的循环引用,由于是单个组件,采用webpack的异步import

    // elements -> grid -> view.vue
    <transition-group class="el-col-list">
      <widget v-for="(element, index) in list" :key="element.key" :index="index" :element="element" />
    </transition-group>
    
    // import Widget from 'components/ButtonView/Widget.vue'
    
    export default {
      components: {
        Widget: () => import('components/ButtonView/Widget.vue'),
      },
    }
    

      栅格列数未与栅格json数据绑定,栅格列表内表单元素是栅格json的一部分,columns数组保存栅格对象,栅格对象参数暂不考虑,只包括list列表字段,draggable双向绑定column.list,未绑定或绑定错误都不能显示。

    // elements -> index.js
    const layout = [
      {
        ...
        type: 'grid',
        name: '栅格布局',
        columns: [
          {
            list: [],
          },
          ...
        ],
      },
    ]
    
    // elements -> grid -> view.vue
    <el-row>
      <el-col v-for="(column, index) in element.columns" :key="index" :span="12">
        <draggable v-model="column.list" ...>
          ...
          <widget
            v-for="(element, index) in column.list"
            :key="element.key"
            :index="index"
            :element="element"
          />
        </draggable>
      </el-col>
    </el-row>
    

      栅格参数可配置水平、垂直排列方式,栅格方式分为flex和响应式,默认为flex,参数具体描述参考 Layout 布局。

    // elements -> index.js
    const layout = [
      {
        ...
        type: 'grid',
        name: '栅格布局',
        options: {
          gutter: 0,
          isFlex: true,
          justify: 'start',
          align: 'top',
        },
        columns: [
          {
            span: 12,
            xs: 12,
            sm: 12,
            md: 12,
            lg: 12,
            xl: 12,
            list: [],
          },
          ...
        ],
      },
    ]
    
    // elements -> grid -> view.vue
    <el-row
      type="flex"
      :gutter="element.options.gutter"
      :justify="element.options.justify"
      :align="element.options.align"
    >
      <el-col
        :xs="element.options.isFlex ? undefined : column.xs"
        :sm="element.options.isFlex ? undefined : column.sm"
        :md="element.options.isFlex ? undefined : column.md"
        :lg="element.options.isFlex ? undefined : column.lg"
        :xl="element.options.isFlex ? undefined : column.xl"
        :span="column.span"
        ...
      >
        ...
      </el-col>
    </el-row>
    

      栅格内元素拖入高亮,类比ViewForm.vue,根据索引找出元素即可。

    // elements -> grid -> view.vue
    <el-row ...>
      <el-col ...>
        <draggable ... @add="handleAdd($event, column)"> ... </draggable>
      </el-col>
    </el-row>
    
    handleAdd({ newIndex }, column) {
      store.commit('SET_SELECT', column.list[newIndex])
    },
    

      类比原始ViewForm.vue,删除元素传参包括索引值、元素列表,Widget.vue声明组件传值data,栅格内也是如此。

    // ViewForm.vue
    <widget
      v-for="(element, index) in data.list"
      :key="element.key"
      :index="index"
      :data="data"
      :element="element"
    />
    
    // elements -> grid -> view.vue
    <widget
      v-for="(element, index) in column.list"
      :key="element.key"
      :index="index"
      :data="column"
      :element="element"
    />
    

      小部件内克隆与ViewForm.vue大同小异,若栅格内部多层嵌套或包含其他表单组件,克隆后不仅要生成副本,而且副本下所有元素的key值不能和之前相同,需递归更新元素的key值。

    // store -> index.js
    CLONE_ELEMENT(state, { index, element, list }) {
      if (el.type === 'grid') {
        resetGridKey(el)
      }
    
      function resetGridKey(element) {
        element.columns.forEach(column => {
          column.list.forEach(el => {
            const key = uuid()
    
            el.key = key
            el.model = el.type + '_' + key
    
            if (el.type === 'grid') {
              resetGridKey(el)
            }
          })
        })
      }
    },
    

    Dialog 公共对话框和 AceEditor

      导入json,可粘贴json数据快速配置表单,点击确定,根据配置的json数据渲染表单,但是数据不限制会发生很多错误,utilsformat.js验证传入的json数据格式是否正确,格式不正确reject并返回错误原因,格式正确更新state内表单数据data

    // layout -> components -> ButtonView.vue
    handleUploadJson() {
      formatJson(this.$refs.uploadAceEditor.getValue())
        .then(json => {
          store.commit('SET_DATA', json)
          this.showUpload = false
        })
        .catch(err => {
          this.$message({
            message: '数据格式有误',
            type: 'error',
            center: true,
          })
          console.error(err)
        })
    },
    

      粘贴json数据,只有用户事先授予网站或应用对剪切板的访问许可后,才能使用异步剪切板读取方法 MDN。使用navigator.clipboard来访问剪切板,readText()异步读取剪切板内容,由于浏览器出于安全考虑,非本地或者网站是http协议,都不能读取剪切板内容。可在httphttps网站控制台打印navigator.clipboardhttp协议网站为undefined。故只有当https网站或者用户授予才可粘贴,否则显示取消按钮,由用户手动粘贴。

    // layout -> components -> ButtonView.vue
    ...
    <template slot="action">
      <el-button v-if="showPasteBtn" size="small" @click="handlePaste">粘贴</el-button>
    
      <el-button v-else size="small" @click="showUpload = false">取消</el-button>
    </template>
    
    ...
    export default {
      data() {
        return {
          showPasteBtn: !!navigator.clipboard,
        }
      },
      methods: {
        ...
        handlePaste() {
          navigator.clipboard.readText().then(res => {
            this.$refs.uploadAceEditor.setValue(res)
          })
        },
      },
    }
    

      清空时清除活动元素select,视图内列表清空。生成json,即显示表单json信息。复制功能引入第三方复制插件clipboard,剪切板实例参数为按钮类名、复制内容,二次封装提示信息,复制完成销毁剪切板实例。

    // layout -> components -> ButtonView.vue
    <el-button ... class="copyJson" @click="handleCopyJson"> 复制</el-button>
    
    handleCopyJson() {
      this.handleCopyText('jsonAceEditor', '.copyJson')
    },
    
    handleCopyText(ref, className) {
      copyText(this.$refs[ref].getValue(), className)
        .then(res => {
          this.$message({
            message: '复制成功',
            type: 'success',
            center: true,
          })
        })
        .catch(err => {
          this.$message({
            message: '复制失败',
            type: 'error',
            center: true,
          })
        })
    },
    
    // utils -> index.js
    function copyText(text, className) {
      const clipboard = new Clipboard(className, {
        text: () => text,
        ...
      })
    
      return new Promise((resolve, reject) => {
        clipboard.on('success', () => {
          resolve()
          clipboard.destroy()
        })
    
        clipboard.on('error', () => {
          reject()
          clipboard.destroy()
        })
      })
    }
    

      设计工具目的是设计json表单数据,某个独立组件传参表单数据渲染为表单。封装独立组件GenerateForm.vueButtonView.vue引入并插入预览弹框插槽,传入全局statedata

    // layout -> components -> ButtonView.vue
    <public-dialog>
      ...
      <generate-form :data="data" />
    </public-dialog>
    
    ...
    export default {
      computed: {
        data() {
          return store.state.data
        },
      },
    }
    
    // components -> ButtonView -> GenerateForm.vue
    <div class="generate-form">
      <el-form ...>
        <component ... />
      </el-form>
    </div>
    
    export default {
      props: {
        data: { ... },
      },
    }
    

      点击预览,单个组件正常渲染(文字、html未显示),栅格内单个组件渲染后残留部分图标。原因是因为栅格组件内部引入的小部件,小部件内部含图标,即预览时栅格内部不应渲染小部件,而应渲染表单元素。GenerateForm.vue批量引入组件,组件传值不可拖动,栅格接收参数,仅渲染为表单元素。栅格内批量引入组件,组件内又包括栅格,即组件循环引用,在beforeCreate再次注册。

    // components -> ButtonView -> GenerateForm.vue
    <component ... :draggable="false" />
    
    // elements -> grid -> view.vue
    <el-col>
      <draggable v-if="draggable"> ... </draggable>
      
      <template v-else>
        <component ...>
      </template>
    </el-col>
    
    import components from 'elements/view'
    
    export default {
      beforeCreate() {
        Object.assign(this.$options.components, components)
      },
      ...
    }
    

      除文字、html外基本可实现预览,获取数据还不可用,表单元素字段属性配置默认值后,ViewForm.vue视图还未显示,但是几乎全部表单元素都将value作为组件传值,小部件传递组件默认值即可显示,栅格内列表也引入的小部件,故栅格内表单元素也会显示默认值。

    // elements -> input -> view.vue
    <input :value="value" />
    
    ...
    export default {
      ...
      props: {
        value: {},
      },
    }
    
    // components -> ButtonView -> Widget.vue
    <component ... :value="element.options.defaultValue" draggable />
    

      点击预览尚不可显示默认值,而且默认值必然与表单组件双向绑定。故需自定义表单组件元素的 v-model,声明传入组件的prop,同时表单值变化触发某个事件的时候,更新prop。文字和html不做双向绑定,但是内部依然可以组件传值value,另外分割线无需组件传值。

    // elements -> input -> view.vue
    <el-input ... :value="value" @input="value => $emit('change', value)" />
    
    export default {
      ...
      model: {
        prop: 'value',
        event: 'change',
      },
      props: {
        ...
        value: {},
      },
    }
    

      GenerateForm.vue内部是el-form,内部引入不同表单元素,表单元素已实现双向绑定,接下来需要与数据绑定,栅格和分割线不用绑定变量,但是栅格内部元素和外部其他元素均绑定modelGenerateForm.vue初始化的models传入栅格,由于对象传值引用,故栅格内部元素也能绑定,栅格内部可能嵌套栅格,models需传递下去。

    // components -> ButtonView -> GenerateForm.vue
    <el-form :model="models" ...>
      <components v-model="models[element.model]" :models="models" ... />
    </el-form>
    
    export default {
      data() {
        return {
          models: {},
        }
      },
      created() {
        this.handleSetModels()
      },
      methods: {
        handleSetModels() {
          const models = {}
    
          getGridModel(this.data.list)
    
          this.models = models
    
          function getGridModel(list) {
            list.forEach(element => {
              if (element.type === 'grid') {
                element.columns.forEach(column => {
                  if (column.list.length) {
                    getGridModel(column.list)
                  }
                })
              } else {
                if (element.type !== 'divider') {
                  models[element.model] = element.options.defaultValue
                }
              }
            })
          }
        },
      },
    }
    
    // elements -> grid -> view.vue
    <components v-model="models[element.model]" :models="models" ... />
    
    export default {
      props: {
        models: { ... },
      },
    }
    

      点击获取数据后,将models数据放入编辑器,GenerateForm.vue内部加入getData方法返回modelsmodels需拷贝副本返回,保证组件内models不被污染。

    // components -> ButtonView -> GenerateForm.vue
    getData() {
      return deepClone(this.models)
    },
    
    // layout -> components -> ButtonView.vue
    <generate-form ref="generateForm" :data="data" ... />
    
    handleGetData() {
      this.models = this.$refs.generateForm.getData()
      this.showPreviewData = true
    },
    

    Tinymce 富文本编辑器

      最初决定使用tinymce作为富文本编辑器主要是由于tinymce操作按钮很容易控制,图片上传很方便,中文文档 也容易上手,不足部分就是依赖tinymce-vue且很多功能声明后还需单独引入才能使用,组件语言部分要单独引入js。其他编辑器图片上传很复杂,并且最主要的是上传的图片大小不可控制,有的文档也不完善,后续可能会替换其他编辑器,wangEditor可作为尝试。

      组件内配置详细可参考源代码,其中图片上传部分详细描述。使用images_upload_handler自定义图片上传,参数分别为blobInfosuccessfailureblobInfo为图片文件详细信息(文件名、base64等),success为图片上传成功回调,传参图片url地址,failure为图片上传失败回调,传参错误描述信息。用户引入GenerateForm.vue不可见自定义图片上传函数体,若要获取文件信息、回调函数只能通过子组件传值父组件,且栅格引入后需逐层向上传递。editorUploadImage函数内可获取文件信息,也可异步调用失败和成功回调函数。

    // elements -> editor -> view.vue
    images_upload_handler: (blobInfo, success, failure) => {
      this.$emit('editor-upload-image', {
        blobInfo,
        success,
        failure,
        model: this.element.model,
      })
    },
    
    // elements -> grid -> view.vue
    <component @editor-upload-image="data => $emit('editor-upload-image', data)" />
    
    // components -> ButtonView -> GenerateForm.vue
    <component @editor-upload-image="data => $emit('editor-upload-image', data)" />
    
    // layout -> components -> ButtonView.vue
    <generate-form ... @editor-upload-image="editorUploadImage" />
    
    editorUploadImage({ blobInfo, success }) {
      success('data:image/jpeg;base64,' + blobInfo.base64())
    },
    

    blank 自定义区域

      若设计工具仅仅支持上述表单组件,设计工具的局限性会非常大,尚不支持Tabs标签页、表格,也不支持引入第三方的表单组件,所以需提供自定义区域插槽,用户再根据实际情况插入不同的表单组件,以此增加表单延展性。

      首先要明确的是,组件内部若有多个同名具名插槽,外部插入元素时,均会插入到同名具名插槽内部。组件外部插入多个不同插槽名元素时,只有和组件内插槽名相同的元素才能插入。GenerateForm.vue初始化时不仅需要创建绑定表单models,还要获取表单内所有自定义区域的model,即是自定义区域内插槽的名称。

      若GenerateForm.vue外部插入ABC等若干个不同插槽名的元素,表单内含自定义区域A、栅格嵌套多层的BGenerateForm.vue根据表单内自定义区域个数创建slots数组(AB),创建AB对应具名插槽,自定义区域A外部AB两个元素待插入,但是由于自定义区域A内部只有插槽A,则A元素插入自定义区域A内部。

    <generate-form>
      <div slot="A">A</div>
      <div slot="B">B</div>
      <div slot="C">C</div>
      ...
    </generate-form>
    
    // generate-form
    <div>
      <blank-A>
        <template slot="A">
          <slot name="A" />
        </template>
        <template slot="B">
          <slot name="B" />
        </template>
      </blank-A>
    </div>
    
    // black-A
    <div>
      <slot name="A" />
    </div>
    

      自定义区域假设嵌套两层栅格,传入slots数组(AB),第一层栅格外部插入AB元素,第一层栅格内部解析为第二层栅格并创建AB插槽,第二层栅格外部插入AB元素,内部解析为自定义区域B并创建AB插槽,由于自定义区域B内部只有插槽B,则B元素插入自定义区域B内部。

    <generate-form>
      <div slot="A">A</div>
      <div slot="B">B</div>
      <div slot="C">C</div>
      ...
    </generate-form>
    
    // generate-form
    <div>
      <grid-1>
        <template slot="A">
          <slot name="A" />
        </template>
        <template slot="B">
          <slot name="B" />
        </template>
      </grid-1>
    </div>
    
    // grid1
    <div>
      <grid-2>
        <template slot="A">
          <slot name="A" />
        </template>
        <template slot="B">
          <slot name="B" />
        </template>
      </grid-2>
    </div>
    
    // grid-2
    <div>
      <black-B>
        <template slot="A">
          <slot name="A" />
        </template>
        <template slot="B">
          <slot name="B" />
        </template>
      </black-B>
    </div>
    
    // black-B
    <div>
      <slot name="B" />
    </div>
    

      上述原理基本接近源代码,页面创建初始化modelsslots。自定义区域将models放在model字段上,无论栅格嵌套多少层,scope.model始终为models,并且models与表单内部models是同一个models,自定义组件双向绑定models变量,预览获取数据始终为表单值models(包括自定义组件)。

    // components -> ButtonView -> GenerateForm.vue
    <component :slots="slots">
      <template v-for="slot in slots" :slot="slot" slot-scope="scope">
        <slot :name="slot" :model="scope.model" />
      </template>
    </component>
    
    handleSetModels() {
      const models = {}
      const slots = []
    
      getGridModel(this.data.list)
    
      this.models = models
      this.slots = slots
    
      function getGridModel(list) {
        list.forEach(element => {
          if (element.type === 'grid') {
            ...
          } else {
            if (element.type === 'blank') {
              slots.push(element.model)
            }
            ...
          }
        })
      }
    },
    
    // elements -> grid -> view.vue
    <component :slots="slots">
      <template v-for="slot in slots" :slot="slot" slot-scope="scope">
        <slot :name="slot" :model="scope.model" />
      </template>
    </component>
    
    // elements -> blank -> view.vue
    <div>
      <slot :name="element.model" :model="models">
        <div class="custom-area">{{ element.model }}</div>
      </slot>
    </div>
    

    表单功能

    重置

      表单重置其实调用el-form resetFields方法基本就能完成重置,但是时间和日期选择器重置存在bug。时间范围下绑定值times,初始值null,选择时间后重置,times值为[ null ],绑定值空数组[],打开时间选择器无法选择时间。日期范围下均存在这种bug,故范围选择方式统一默认值null,表单内部重置重写一次。

    <el-form ref="form" :model="form">
      <el-form-item prop="times" label="时间范围">
        <el-time-picker v-model="form.times" value-format="HH-mm-ss" is-range></el-time-picker>
      </el-form-item>
    </el-form>
    
    <el-button @click="$refs.form.resetFields()">重置</el-button>
    
    export default {
      data() {
        return {
          form: {
            times: null,
          },
        }
      },
    }
    
    // components -> ButtonView -> GenerateForm.vue
    reset() {
      this.$refs.modelsForm.resetFields()
      this.resetTimePicker()
    },
    
    resetTimePicker() {
      for (const key of Object.keys(this.models)) {
        if (
          Array.isArray(this.models[key]) &&
          this.models[key].length === 1 &&
          this.models[key][0] === null
        ) {
          this.models[key] = null
        }
      }
    },
    

    校验规则

      校验规则大多数涉及必填,只有单行文本和多行文本较为特殊。单行文本支持验证器验证和正则表达式验证,多行文本支持正则表达式验证。必填字段星号可由组件传值el-fom-item required属性控制。

    <el-form-item :required="element.options.required">...</el-form-item>
    

      element-ui el-form表单验证方式包括四种,必填方式最为常用,指定required true开启必填,正则表达式方式pattern可直接指定正则表达式,验证器方式包括字符串string、数字number、整数interger、浮点数floatURL类型、16进制hex、电子邮箱email等,自定义验证方式最灵活,可定制化设置验证规则,详细参考 element-ui

    rules: {
      name: [{ required: true, message: '请输入姓名' }],
      phone: [{ pattern: /^1[3456789]d{9}$/, message: '格式有误' }],
      email: [{ type: 'email', message: '格式有误' }],
      psw: [{ validator: validatePsw }],
    },
    

      表单初始化时不仅创建modelsslots,验证规则rules也要创建。所有属性均会添加必填方式,但是requiredfalse不会开启必填。isPattern为正则验证方式,其中单行文本和多行文本独有,new RegExp实例化正则。

    // components -> ButtonView -> GenerateForm.vue
    <el-form />
    
    function handleSetModels() {
      const rules = {}
      ...
      getGridModel(this.data.list)
    
      this.rules = rules
    
      function getGridModel(list) {
        list.forEach(element => {
          if (element.type === 'grid') {
            ...
          } else {
            rules[element.model] = [
              {
                required: !!element.options.required,
                message: element.options.requiredMessage || `请输入${element.name}`,
              },
            ]
    
            if (element.options.isType) {
              rules[element.model].push({
                type: element.options.type,
                message: element.options.typeMessage || `${element.name}验证不匹配`,
              })
            }
    
            if (element.options.isPattern) {
              rules[element.model].push({
                pattern: new RegExp(element.options.pattern),
                message: element.options.patternMessage || `${element.name}格式不匹配`,
              })
            }
          }
        })
      }
    }
    

      验证器方式中数字、整数、浮点数较为特殊,若指定其中一种,表单验证是不会通过的,因为单行文本双向绑定值始终为字符串,不会为数字类型。解决方式指定需指定el-input为数字类型,且触发change事件需转换为数值。

    <common-view ...>
      <el-input
        v-if="element.options.isType && ['number', 'integer', 'float'].includes(element.options.type)"
        type="number"
        @input="input"
      />
    
      <el-input v-else @input="input" />
    </common-view>
    
    input(value) {
      const { type, isType } = this.element.options
    
      if (isType && ['number', 'integer', 'float'].includes(type) && value !== '') {
        value = Number(value)
      }
    
      this.$emit('change', value)
    },
    

    生成代码

      生成代码部分需要根据表单json往固定模板填充数据,默认包括提交、重置按钮,组件传参jsonData为表单json数据,editData为表单初始值,remoteOption是级联选择器列表数据。

    // elements -> cascader -> view.vue
    <el-cascader ... :options="remoteOption && remoteOption[element.options.remoteOption]" />
    

      若组件使用了富文本编辑器,默认包含事件editor-upload-image及其对应处理函数。

    editorUploadImage({ model, blobInfo, success, failure }) {
      // success(图片地址) / failure(失败说明) 可异步调用
      // success('http://xxx.xxx.xxx/xxx/image-url.png')
      // failure('上传失败')
      success('data:image/jpeg;base64,' + blobInfo.base64())
    }, 
    

      插槽部分根据slots列表插入组件内部,默认包含插槽名和变量绑定方式。editData传入组件内部,GenerateForm.vue内部需要合并默认值和editData

    this.models = Object.assign(models, deepClone(this.value))
    

    HTML 默认值

      html默认值最初放置的是多行文本框,现在引入AceEditor后需要调整。AceEditor内部修改值后需更新html默认值,通过change事件触发。

    // compoents -> AceEditor.vue
    this.editor.session.on('change', delta => {
      this.$emit('change', this.getValue())
    })
    
    // elements -> html -> config.vue
    <ace-editor ... @change="handelChange" />
    
    handelChange(text) {
      this.data.options.defaultValue = text
    },
    

      若表单内包括多个html组件,切换组件发现AceEditor内部值并未发生改变。html字段属性始终是同一个AceEditor,所以值不会发生改变。内部需要监听html对象的改变,调用组件内部setValue方法赋值。元素拖入立即执行监听hander,默认对象非深度监听,但是不能开启深度监听,原因是因为组件内部change事件触发更新默认值时,开启深度监听后,hander会触发再次调用组件内setValuesetValue再次触发change,会造成循环调用页面卡死。即监听对象引用改变,不监听对象内容改变即可。

    data: {
      handler() {
        this.$nextTick(() => {
          this.$refs.htmlAceEditor.setValue(this.data.options.defaultValue)
        })
      },
      deep: false,
      immediate: true,
    },
    

    发布维护

    NPM 组件

      为方便后期使用,可将项目发布为npm包。与main.js同级目录创建index.js,引入需导出的组件和样式。Vue.use(cpn)默认调用cpninstall方法注册组件,参数默认为Vue

    // index.js
    const components = [GenerateForm, MakingForm]
    
    const install = Vue => {
      components.forEach(component => {
        Vue.component(component.name, component)
      })
    }
    
    export default {
      install,
    }
    
    // 引用方式
    import DwFormMaking from 'dw-form-making'
    Vue.use(DwFormMaking)
    

      组件部分引入方式。

    // index.js
    import GenerateForm from 'components/ButtonView/GenerateForm'
    import MakingForm from './layout/index'
    
    export { GenerateForm, MakingForm }
    
    // 调用方式
    import { GenerateForm, MakingForm } from 'dw-form-making'
    
    Vue.component(GenerateForm.name, GenerateForm)
    Vue.component(MakingForm.name, MakingForm)
    

      新增script命令,name为构建名称,最后一个参数为入口文件。参数以及构建库输出文件详细参考 vue-cli 库。

    // package.json
    "scripts": {
      ...
      "publish": "vue-cli-service build --target lib --name DwFormMaking ./src/index.js",
    },
    

      配置package.json,详细描述发布包所需字段。

    • name:包名,名字是唯一的,可在npm官网查询是否重复
    • description:描述,npm官网查询出包后的描述信息
    • version:版本号,每次发布版本号不能和历史版本号相同
    • author:作者
    • private:是否私有,false公开才能发布到npm
    • keywords:关键字,通常用于npm关键字搜索
    • main:入口文件,指向编译后的包文件
    • filesnpm白名单,只有files中指定的文件或文件夹会被打包到项目中

      配置完成后,运行npm run publish会生成相关的包文件,且默认位于dist目录下。

    在这里插入图片描述

      申请npm官方账号后,运行npm login,输入用户名、密码和邮箱,注意密码输入后是不可见的,输入后回车请求登录到npm

    在这里插入图片描述

      登录成功后控制台输出如下。

    在这里插入图片描述

      紧接着再运行npm publish命令,将package.jsonfiles属性指定的文件发布到npm

    在这里插入图片描述

      控制台输出如下命令,即表示发布成功,后续邮箱中也会收到发布成功的邮件。

    在这里插入图片描述

      现在就可以运行npm i dw-form-making下载刚才发布的包了,展开node_modulesdw-form-making文件夹,果然是将files指定的文件发布了。

    在这里插入图片描述

    Git 远程库维护

      GitHub新建仓库后,本地再关联远程仓库。

    git remote add origin https://github.com/username/repository.git
    

      将本地master分支推送至远程仓库。

    git push origin master
    

    在线预览

      GitHub提供了静态页面的预览功能,并默认展示分支下的index.html文件。

      因此可以单独创建一个page分支。

    git branch page
    

      然后运行npm run build,删除目录内除libnode_modules.gitignore外的所有文件,移动lib内文件到外层,最终根目录结构大致如下。

    在这里插入图片描述

      暂存并提交page分支的修改,推送至远程仓库。

    git push origin page
    

      假设上述都没有问题,那么将会在GitHub查看到如下分支结构。

    在这里插入图片描述

      点击仓库内Settings按钮,找到侧边Pages项。

    在这里插入图片描述

      选择page分支,root根目录,点击Save

    在这里插入图片描述

      呐,你的预览页就已经发布到https://xxx.github.io/dw-form-making/了,点击试试吧。

    在这里插入图片描述

    后记

      开源的表单设计器基础版本使用范围很小,设计器内部非常多的bug,最为基本的栅格也不支持。某天空闲突然对其源码感兴趣,大致梳理发现其业务逻辑繁杂,组件层级非常深,部分组件代码冗余,甚至单个组件内部代码接近500行,可读性和拓展性很差。于是参考其样式,直接重构了js部分。

      最为基础的表单组件基本实现并可预览,较为复杂的栅格布局需要仔细梳理,理解了其中栅格的递归嵌套逻辑,很快就能实现。基于此为基础的自定义区域,也就是递归组件内的作用域插槽,最为耗时,由于是空闲时间做的工具,工作时间稍微有点想法会实现一个demo,考虑过render函数,也考虑过缩小组件层级,最终的插槽v-for也是某个时刻偶然想到的。项目之前包括选择树、代码编辑器,仔细考虑后决定删除。最大原因还是为了缩小项目体积,其中组件更多不过是完善,差异也只是大同小异,不同基本组件都涉及,定制组件自定义引入,简单而不简单。

      项目整体难度也不高,此笔记仅是记录重构过程的部分思路。重构初一方面由于兴趣使然,另一方面对其内部逻辑和npm包的发布新奇。整体下来可以巩固element-ui表单组件的使用,部分其它组件也有涉及,对于页面布局、类名的设定、代码规范都是一次练习。其中递归组件、作用域插槽、组件循环引用较复杂,仔细梳理也能明白其中原理。代码管理方面也可巩固Git基本命令的使用、多远程库的管理。

      工具可在线预览或克隆,之前Git提交次数过多导致版本库较大,已重新创建了仓库。源代码在GitHub开源,工具名 dw-form-making

    更新日志

    2020/12/11 17:18

      可能你也注意到了,代码中用到store的地方都是引入再使用,为什么不放在vue原型对象prototype上,代码中将最大程度还原vuex的调用方式。

    import store from './store'
    Vue.prototype.$store = store
    

      但是发布为 vue 插件,store放在vue的原型上不是那么理想,首先来看看组件install方法,第一个参数是Vue构造器,也就是执行Vue.use()时的Vue,倘若像上述给予Vue原型上添加$store,有一个很糟糕的情况,则是$store关键字被占用了,页面只有单独定义其他关键字,否则$store直接被覆盖掉。更糟糕的情况则是引用vuex状态管理的项目,由于vuexbeforeCreate首行注入$store,若同时集成表单工具,可能会导致工具崩溃,出现意料之外的bug

      此次更新修复了栅格复制key值的bug,删除掉组件内部分未引用的变量。并且在工具内控制台输出了彩蛋(试一试),不包括npm组件。

    2022/03/05 17:35

      虽然是个人维护的小项目,代码风格大多数都是统一的,但是也会存在很多不合理的风格。因此根据 vscode 插件与 npm 包,保存时自动修复代码格式错误 一文,引入了eslintprettier来规范和格式化项目的代码。组件内props传值类型和默认值也极为不合理,通过eslint检测已经全部修复。

      另外在仓库下新增了deploy.sh脚本,主要用来快捷部署Pages静态页面。

      由于GitHub的仓库经常访问缓慢或者无响应,因此利用Gitee镜像了此仓库(看一看)。

    🎉 写在最后

    🍻伙伴们,如果你已经看到了这里,觉得这篇文章有帮助到你的话不妨点赞👍或 Star ✨支持一下哦!

    手动码字,如有错误,欢迎在评论区指正💬~

    你的支持就是我更新的最大动力💪~

    GitHub / GiteeGitHub Pages掘金CSDN 同步更新,欢迎关注😉~

    展开全文
  • f-render 是基于 vue-ele-form 开发的可视化表单设计工具, 适用于 各种流程引擎和动态表单项目,大大节省你的开发时间;注意,此设计器不是独立存在的,是依托于 vue-ele-form,在使用前请务必阅读 vue-ele-form 的...
  • vue-ele-form-generator是专为vue-ele-form开发的可视化表单设计工具, 让表单开发的效率更上一层楼
  • formbuilder表单构建工具_在线表单设计器代码
  • 一个基于.NET C# 开发的 Winform 自定义表单设计组件。支持Xml保存和加载表单,支持控件的拖放和属设置,支持复制、粘贴、对齐、撤销、重做等设计时操作。
  • 自定义表单设计器演示版源码(C#).rar.rar
  • 这个资源是一个vue+element UI的表单设计器,可以用它来实现前后端分离的前台设计,参考 http://ctjee.com/customized.html#mod9
  • 初假花了近两周时间实现了期末前的一个想法:做一个在线设计表单,采集信息并能转换结果的工具。用的是SpringBoot + MyBatis + JQuery + Bootstrap,前一步每个细节都一个人忙活还蛮累的,不过很充实,把点子一步步...
  • 表单设计Javascript工具

    2017-03-03 10:45:22
    javascript 表单设计工具

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 107,065
精华内容 42,826
关键字:

表单设计工具

友情链接: input100.rar