Explorar el Código

表格快速构建更新版本

fangq hace 4 años
padre
commit
d2b93d8a0a

+ 34 - 6
packages/FormConfig.vue

@@ -37,11 +37,29 @@
                          placeholder="项之间的间隔"
                          style="width: 100%"></el-input-number>
       </el-form-item>
+      <el-form-item label="多分组转标签">
+        <el-switch v-model="data.tabs"
+                   active-color="#409EFF"></el-switch>
+      </el-form-item>
+      <el-form-item label="详情模式">
+        <el-switch v-model="data.detail"></el-switch>
+      </el-form-item>
+      <!-- <el-form-item label="标签样式"
+                    v-if="data.tabs">
+        <el-select v-model="data.tabsType">
+          <el-option label="基础"
+                     value=""></el-option>
+          <el-option label="卡片"
+                     value="card"></el-option>
+          <el-option label="卡片2"
+                     value="border-card"></el-option>
+        </el-select>
+      </el-form-item> -->
       <el-form-item label="显示按钮">
         <el-switch v-model="data.menuBtn"
                    active-color="#409EFF"></el-switch>
       </el-form-item>
-      <el-form-item label="按钮位置"
+      <!-- <el-form-item label="按钮位置"
                     v-if="data.menuBtn">
         <el-select v-model="data.menuPostion"
                    placeholder="按钮位置">
@@ -52,13 +70,13 @@
           <el-option label="居右"
                      value="right"></el-option>
         </el-select>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="显示提交按钮"
                     v-if="data.menuBtn">
         <el-switch v-model="data.submitBtn"
                    active-color="#409EFF"></el-switch>
       </el-form-item>
-      <el-form-item label="提交按钮的大小"
+      <!-- <el-form-item label="提交按钮的大小"
                     v-if="data.menuBtn && data.submitBtn">
         <el-select v-model="data.submitSize"
                    placeholder="提交按钮的大小">
@@ -69,7 +87,7 @@
           <el-option label="超小"
                      value="mini"></el-option>
         </el-select>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="提交按钮的文字"
                     v-if="data.menuBtn && data.submitBtn">
         <el-input v-model="data.submitText"
@@ -81,7 +99,7 @@
         <el-switch v-model="data.emptyBtn"
                    active-color="#409EFF"></el-switch>
       </el-form-item>
-      <el-form-item label="清空按钮的大小"
+      <!-- <el-form-item label="清空按钮的大小"
                     v-if="data.menuBtn && data.emptyBtn">
         <el-select v-model="data.emptySize"
                    placeholder="提交按钮的大小">
@@ -92,12 +110,22 @@
           <el-option label="超小"
                      value="mini"></el-option>
         </el-select>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item label="清空按钮的文字"
                     v-if="data.menuBtn && data.emptyBtn">
         <el-input v-model="data.emptyText"
                   placeholder="提交按钮的文字"></el-input>
       </el-form-item>
+      <el-form-item label="全局只读">
+        <el-switch v-model="data.readonly"></el-switch>
+      </el-form-item>
+      <el-form-item label="全局禁用">
+        <el-switch v-model="data.disabled"></el-switch>
+      </el-form-item>
+      <!-- <el-form-item label="全局栅格">
+        <el-input v-model="data.span"
+                  placeholder="全局栅格"></el-input>
+      </el-form-item> -->
     </el-form>
   </div>
 </template>

+ 175 - 61
packages/WidgetConfig.vue

@@ -2,61 +2,171 @@
   <div class="widget-config">
     <el-form label-suffix=":"
              v-if="this.data && Object.keys(this.data).length > 0"
+             labelPosition="left"
+             labelWidth="90px"
              size="small">
-      <el-form-item label="类型">
-        <el-select v-model="data.type"
-                   placeholder="请选择类型"
-                   @change="handleChangeType">
-          <el-option-group v-for="group in fields"
-                           :key="group.title"
-                           :label="group.title">
-            <el-option v-for="item in group.list"
-                       :key="item.type"
-                       :label="item.label"
-                       :value="item.type">
-            </el-option>
-          </el-option-group>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="属性值">
-        <el-input v-model="data.prop"
-                  placeholder="属性值"></el-input>
-      </el-form-item>
-      <el-form-item label="标题">
-        <el-input v-model="data.label"
-                  placeholder="标题"></el-input>
-      </el-form-item>
-      <el-form-item label="宽度"
-                    v-if="data.subfield">
-        <el-input-number v-model="data.width"
-                         controls-position="right"
-                         placeholder="宽度"
-                         :min="100"></el-input-number>
-      </el-form-item>
-      <el-form-item label="表单栅格"
-                    v-if="!data.subfield && !['group'].includes(data.type)">
-        <el-input-number v-model="data.span"
-                         controls-position="right"
-                         placeholder="表单栅格"
-                         :min="8"
-                         :max="24"></el-input-number>
-      </el-form-item>
-      <el-form-item label="数据类型"
-                    v-if="['cascader','checkbox','upload','img','array'].includes(data.type)">
-        <el-select v-model="data.dataType"
-                   placeholder="数据类型"
-                   clearable>
-          <el-option label="String"
-                     value="string"></el-option>
-          <el-option label="Number"
-                     value="number"></el-option>
-        </el-select>
-        &nbsp;<a href="https://avuejs.com/doc/dataType"
-           target="_blank"
-           style="color: #409EFF;">详情</a><br>
-      </el-form-item>
-      <component :is="getComponent"
-                 :data="data"></component>
+      <el-collapse v-model="collapse">
+        <el-collapse-item name="1"
+                          title="基本属性">
+          <el-form-item label="类型"
+                        v-if="data.type && !data.component">
+            <el-select v-model="data.type"
+                       style="width:100%;"
+                       placeholder="请选择类型"
+                       @change="handleChangeType">
+              <el-option-group v-for="group in fields"
+                               :key="group.title"
+                               :label="group.title">
+                <el-option v-for="item in group.list"
+                           :key="item.type"
+                           :label="item.label"
+                           :value="item.type">
+                </el-option>
+              </el-option-group>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="属性值">
+            <el-input v-model="data.prop"
+                      clearable
+                      placeholder="属性值"></el-input>
+          </el-form-item>
+          <el-form-item label="标题">
+            <el-input v-model="data.label"
+                      clearable
+                      placeholder="标题"></el-input>
+          </el-form-item>
+          <el-form-item label="宽度"
+                        v-if="data.subfield">
+            <el-input-number style="width:100%;"
+                             v-model="data.width"
+                             controls-position="right"
+                             placeholder="宽度"
+                             :min="100"></el-input-number>
+          </el-form-item>
+          <el-form-item label="表单栅格"
+                        v-if="!data.subfield && !['group'].includes(data.type)">
+            <el-input-number style="width:100%;"
+                             v-model="data.span"
+                             controls-position="right"
+                             placeholder="表单栅格"
+                             :min="8"
+                             :max="24"></el-input-number>
+          </el-form-item>
+          <el-form-item label="数据类型"
+                        v-if="['cascader','checkbox','radio','select','tree','upload','img','array','slider','timerange','daterange','datetimerange'].includes(data.type)">
+            <template slot="label">
+              <el-link :underline="false"
+                       href="https://avuejs.com/doc/dataType"
+                       target="_blank">数据类型 <i class="el-icon-question"></i></el-link>
+            </template>
+            <el-select v-model="data.dataType"
+                       placeholder="数据类型"
+                       clearable>
+              <el-option label="String"
+                         value="string"></el-option>
+              <el-option label="Number"
+                         value="number"></el-option>
+              <el-option label="Array"
+                         value="array"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="深结构"
+                        v-if="data.type && !data.component">
+            <template slot="label">
+              <el-link :underline="false"
+                       href="https://avuejs.com/doc/form/form-bind"
+                       target="_blank">深结构 <i class="el-icon-question"></i></el-link>
+            </template>
+            <el-input v-model="data.bind"
+                      clearable
+                      placeholder="深结构"></el-input>
+          </el-form-item>
+          <el-form-item label="字段提示">
+            <template slot="label">
+              <el-link :underline="false"
+                       href="https://avuejs.com/doc/form/form-tip"
+                       target="_blank">字段提示 <i class="el-icon-question"></i></el-link>
+            </template>
+            <el-input v-model="data.tip"
+                      clearable
+                      placeholder="字段提示"></el-input>
+          </el-form-item>
+          <el-form-item v-if="data.tip && !['upload'].includes(data.type)"
+                        label="字段提示位置"
+                        label-width="110px">
+            <el-select v-model="data.tipPlacement"
+                       placeholder="字段提示位置"
+                       clearable>
+              <el-option label="上"
+                         value="top"></el-option>
+              <el-option label="下"
+                         value="bottom"></el-option>
+              <el-option label="左"
+                         value="left"></el-option>
+              <el-option label="右"
+                         value="right"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="标题提示">
+            <template slot="label">
+              <el-link :underline="false"
+                       href="https://avuejs.com/doc/form/form-tip"
+                       target="_blank">标题提示 <i class="el-icon-question"></i></el-link>
+            </template>
+            <el-input v-model="data.labelTip"
+                      clearable
+                      placeholder="标题提示"></el-input>
+          </el-form-item>
+          <el-form-item v-if="data.labelTip && !['upload'].includes(data.type)"
+                        label="标题提示位置"
+                        label-width="110px">
+            <el-select v-model="data.labelTipPlacement"
+                       placeholder="标题提示位置"
+                       clearable>
+              <el-option label="上"
+                         value="top"></el-option>
+              <el-option label="下"
+                         value="bottom"></el-option>
+              <el-option label="左"
+                         value="left"></el-option>
+              <el-option label="右"
+                         value="right"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item v-if="!['color', 'dynamic', 'group', 'ueditor', 'upload', 'map'].includes(data.type)"
+                        label="默认值">
+            <template v-if="defaultValues && defaultValues[data.type]">
+              <el-select v-model="data.value"
+                         allow-create
+                         clearable
+                         filterable
+                         placeholder="默认值">
+                <el-option v-for="item in defaultValues[data.type]"
+                           :key="item.value"
+                           :label="item.label"
+                           :value="item.value">
+                </el-option>
+              </el-select>
+            </template>
+            <template v-else>
+              <el-input v-model="data.value"
+                        clearable
+                        placeholder="默认值"></el-input>
+            </template>
+          </el-form-item>
+          <el-form-item label="详情模式">
+            <el-switch v-model="data.detail"></el-switch>
+          </el-form-item>
+          <component :is="getComponent"
+                     :data="data"
+                     :default-values="defaultValues"></component>
+        </el-collapse-item>
+        <el-collapse-item name="2"
+                          title="事件属性"
+                          v-if="!['group'].includes(data.type)">
+          <config-event :data="data"></config-event>
+        </el-collapse-item>
+      </el-collapse>
     </el-form>
     <avue-empty v-else
                 desc="拖拽字段进行配置"
@@ -73,28 +183,32 @@ const dateArr = [
 
 export default {
   name: 'widget-config',
-  props: ['data'],
+  props: ['data', 'defaultValues'],
   computed: {
-    getComponent () {
+    getComponent() {
       const prefix = 'config-'
-      const { type } = this.data
+      const { type, component } = this.data
+      if ((!type || component) && type != 'ueditor') return prefix + 'custom'
       let result = 'input'
 
       if ([undefined, 'input', 'password', 'url'].includes(type)) result = 'input'
       else if (dateArr.includes(type)) result = 'date'
       else if (['array', 'img'].includes(type)) result = 'array'
+      else if (['tree', 'cascader'].includes(type)) result = 'tree'
+      else if (['radio', 'checkbox', 'select'].includes(type)) result = 'select'
       else result = type
 
       return prefix + result
     }
   },
-  data () {
+  data() {
     return {
-      fields
+      fields,
+      collapse: ['1', '2']
     }
   },
   methods: {
-    async handleChangeType (type) {
+    async handleChangeType(type) {
       if (type) {
         const config = await this.getConfigByType(type);
         config.prop = this.data.prop;
@@ -106,7 +220,7 @@ export default {
         }
       }
     },
-    getConfigByType (type) {
+    getConfigByType(type) {
       return new Promise((resolve, reject) => {
         fields.forEach(field => {
           field.list.forEach(config => {

+ 20 - 7
packages/WidgetForm.vue

@@ -12,7 +12,8 @@
                    :group="{ name: 'form' }"
                    ghost-class="ghost"
                    :animation="300"
-                   @add="handleWidgetAdd">
+                   @add="handleWidgetAdd"
+                   @end="$emit('change')">
           <template v-for="(column, index) in data.column">
             <div class="widget-form-table"
                  v-if="column.type == 'dynamic'"
@@ -22,7 +23,8 @@
               <widget-form-table :data="data"
                                  :column="column"
                                  :index="index"
-                                 :select.sync="selectWidget"></widget-form-table>
+                                 :select.sync="selectWidget"
+                                 @change="$emit('change')"></widget-form-table>
             </div>
             <div class="widget-form-group"
                  v-else-if="column.type == 'group'"
@@ -32,7 +34,8 @@
               <widget-form-group :data="data"
                                  :column="column"
                                  :index="index"
-                                 :select.sync="selectWidget">
+                                 :select.sync="selectWidget"
+                                 @change="$emit('change')">
               </widget-form-group>
             </div>
             <el-col v-else
@@ -42,10 +45,12 @@
                     :offset="column.offset || 0">
               <el-form-item class="widget-form-item"
                             :label="column.label"
+                            :labelWidth="column.labelWidth"
                             :prop="column.prop"
                             :class="{ active: selectWidget.prop == column.prop, 'required': column.required }"
                             @click.native="handleSelectWidget(index)">
-                <widget-form-item :item="column"></widget-form-item>
+                <widget-form-item :item="column"
+                                  :params="column.params"></widget-form-item>
                 <el-button title="删除"
                            @click.stop="handleWidgetDelete(index)"
                            class="widget-action-delete"
@@ -96,13 +101,19 @@ export default {
       this.selectWidget = this.data.column[index]
     },
     handleWidgetAdd (evt) {
-      const newIndex = evt.newIndex;
-      const data = this.deepClone(this.data.column[newIndex]);
+      const newIndex = evt.newIndex
+      const data = this.deepClone(this.data.column[newIndex])
       if (!data.prop) data.prop = Date.now() + '_' + Math.ceil(Math.random() * 99999)
       delete data.icon
       delete data.subfield
-      this.$set(this.data.column, newIndex, { ...data })
+      if (data.type == 'title') {
+        delete data.label
+        this.form[data.prop] = data.value
+      }
+      this.$set(this.data.column, newIndex, data)
       this.handleSelectWidget(newIndex)
+
+      this.$emit("change")
     },
     handleWidgetDelete (index) {
       if (this.data.column.length - 1 === index) {
@@ -112,6 +123,7 @@ export default {
 
       this.$nextTick(() => {
         this.data.column.splice(index, 1)
+        this.$emit("change")
       })
     },
     handleWidgetClone (index) {
@@ -120,6 +132,7 @@ export default {
       this.data.column.splice(index, 0, cloneData)
       this.$nextTick(() => {
         this.handleSelectWidget(index + 1)
+        this.$emit("change")
       })
     },
   },

+ 18 - 6
packages/WidgetFormGroup.vue

@@ -9,7 +9,8 @@
                :group="{ name: 'form' }"
                ghost-class="ghost"
                :animation="200"
-               @add="handleWidgetGroupAdd($event, column)">
+               @add="handleWidgetGroupAdd($event, column)"
+               @end="$emit('change')">
       <template v-for="(item, groupIndex) in column.children.column">
         <div class="widget-form-table"
              v-if="item.type == 'dynamic'"
@@ -19,7 +20,8 @@
           <widget-form-table :data="column.children"
                              :column="item"
                              :index="groupIndex"
-                             :select.sync="selectWidget"></widget-form-table>
+                             :select.sync="selectWidget"
+                             @change="$emit('change')"></widget-form-table>
         </div>
         <el-col v-else
                 :key="groupIndex"
@@ -31,7 +33,8 @@
                         :prop="item.prop"
                         :class="{ active: selectWidget.prop == item.prop, 'required': item.required }"
                         @click.native.stop="handleWidgetTableSelect(item)">
-            <widget-form-item :item="item"></widget-form-item>
+            <widget-form-item :item="item"
+                              :params="column.params"></widget-form-item>
             <el-button title="删除"
                        @click.stop="handleWidgetTableDelete(column, groupIndex)"
                        class="widget-form-group__item--delete"
@@ -42,7 +45,7 @@
               <i class="iconfont icon-delete"></i>
             </el-button>
             <el-button title="复制"
-                       @click.stop="handleWidgetTableClone(column, groupIndex)"
+                       @click.stop="handleWidgetTableClone(column, item)"
                        class="widget-form-group__item--clone"
                        v-if="selectWidget.prop == item.prop"
                        circle
@@ -89,11 +92,12 @@
 <script>
 import WidgetFormItem from './WidgetFormItem'
 import WidgetFormTable from './WidgetFormTable'
+import draggable from 'vuedraggable'
 
 export default {
-  name: 'widget-form-table',
+  name: 'widget-form-group',
   props: ['data', 'column', 'select', 'index'],
-  components: { WidgetFormItem, WidgetFormTable },
+  components: { WidgetFormItem, WidgetFormTable, draggable },
   data () {
     return {
       selectWidget: this.select,
@@ -106,6 +110,8 @@ export default {
     handleWidgetClear (index) {
       this.data.column[index].children.column = []
       this.selectWidget = this.data.column[index]
+
+      this.$emit("change")
     },
     handleWidgetDelete (index) {
       if (this.data.column.length - 1 === index) {
@@ -115,6 +121,7 @@ export default {
 
       this.$nextTick(() => {
         this.data.column.splice(index, 1)
+        this.$emit("change")
       })
     },
     handleWidgetCloneTable (index) {
@@ -126,6 +133,7 @@ export default {
       this.data.column.splice(index, 0, cloneData)
       this.$nextTick(() => {
         this.handleSelectWidget(index + 1)
+        this.$emit("change")
       })
     },
     handleWidgetTableSelect (data) {
@@ -137,6 +145,7 @@ export default {
       this.$set(column.children.column, column.children.column.length, { ...data })
       this.$nextTick(() => {
         this.selectWidget = column.children.column[column.children.column.length - 1]
+        this.$emit("change")
       })
     },
     handleWidgetTableDelete (column, index) {
@@ -146,6 +155,7 @@ export default {
       } else this.selectWidget = column.children.column[index + 1]
       this.$nextTick(() => {
         column.children.column.splice(index, 1)
+        this.$emit("change")
       })
     },
     handleWidgetGroupAdd (evt, column) {
@@ -165,6 +175,8 @@ export default {
       else data.span = 12
       this.$set(column.children.column, newIndex, { ...data })
       this.selectWidget = column.children.column[newIndex]
+
+      this.$emit("change")
     }
   },
   watch: {

+ 39 - 98
packages/WidgetFormItem.vue

@@ -1,98 +1,39 @@
 <template>
-  <component :is="getComponent(item.type, item.component)"
-             v-model="form[item.prop]"
-             :item="item"
-             :action="item.action"
-             :append="item.append"
-             :accordion="item.accordion"
-             :typeslot="item.typeslot"
-             :appendClick="item.appendClick"
-             :border="item.border"
-             :change="item.change"
-             :checked="item.checked"
-             :clearable="item.clearable"
-             :changeOnSelect="item.changeOnSelect"
-             :click="item.click"
-             :onRemove="item.onRemove"
-             :showWordLimit="item.showWordLimit"
-             :colors="item.colors"
-             :canvasOption="item.canvasOption"
-             :controls-position="item.controlsPosition"
-             :dataType="item.dataType"
-             :defaultExpandAll="item.defaultExpandAll"
-             :defaultTime="item.defaultTime"
-             :dic="item.dicData"
-             :dicUrl="item.dicUrl"
-             :dicMethod="item.dicMethod"
-             :dicQuery="item.dicQuery"
-             :disabled="item.disabled"
-             :drag="item.drag"
-             :endPlaceholder="item.endPlaceholder"
-             :expand-trigger="item.expandTrigger"
-             :filter="item.filter"
-             :blur="item.blur"
-             :focus="item.focus"
-             :tpyeformat="item.tpyeformat"
-             :filesize="item.filesize"
-             :filterable="item.filterable"
-             :format="item.format"
-             :formatTooltip="item.formatTooltip"
-             :iconClasses="item.iconClasses"
-             :label="item.label"
-             :limit="item.limit"
-             :listType="item.listType"
-             :loadText="item.loadText"
-             :min="item.min"
-             :max="item.max"
-             :minlength="item.minlength"
-             :maxlength="item.maxlength"
-             :minRows="item.minRows"
-             :maxRows="item.maxRows"
-             :multiple="item.multiple"
-             :nodeClick="item.nodeClick"
-             :options="item.options"
-             :oss="item.oss"
-             :parent="item.parent"
-             :pickerOptions="item.pickerOptions"
-             :placeholder="item.placeholder || getPlaceholder(item)"
-             :precision="item.precision"
-             :prefixIcon="item.prefixIcon"
-             :prepend="item.prepend"
-             :prependClick="item.prependClick"
-             :prop="item.prop"
-             :props="item.props"
-             :propsHttp="item.propsHttp"
-             :range="item.range"
-             :iconList="item.iconList"
-             :readonly="item.readonly"
-             :checkStrictly="item.checkStrictly"
-             :separator="item.separator"
-             :showFileList="item.showFileList"
-             :showInput="item.showInput"
-             :showStops="item.showStops"
-             :showAllLevels="item.showAllLevels"
-             :showText="item.showText"
-             :size="item.size || 'small'"
-             :startPlaceholder="item.startPlaceholder"
-             :step="item.step"
-             :suffixIcon="item.suffixIcon"
-             :texts="item.texts"
-             :tip="item.tip"
-             :type="item.type"
-             :accept="item.accept"
-             :tags="item.tags"
-             :value-format="item.valueFormat"
-             :voidIconClass="item.voidIconClass"
-             :remote="item.remote"
-             :autocomplete="item.autocomplete"
-             :allow-create="item.allowCreate"
-             :default-first-option="item.defaultFirstOption"
-             :is-img="item.isImg"></component>
+  <div>
+    <span v-if="item.type == 'title'"
+          :style="item.styles"
+          style="margin-left: 5px;">
+      {{item.value}}
+    </span>
+    <component v-else
+               :is="getComponent(item.type, item.component)"
+               v-bind="Object.assign(deepClone(item), params, { size:item.size || 'small' })"
+               :multiple="false"
+               :placeholder="item.placeholder || getPlaceholder(item)"
+               :dic="item.dicData"
+               :value="['time', 'timerange', 'checkbox'].includes(item.type) ? item.dicData: undefined">
+      <span v-if="params.html"
+            v-html="params.html"></span>
+    </component>
+  </div>
 </template>
 <script>
 export default {
   name: 'widget-form-item',
-  props: ['item'],
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    params: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    }
+  },
   data () {
     return {
       form: {}
@@ -102,8 +43,8 @@ export default {
     getComponent (type, component) {
       let KEY_COMPONENT_NAME = 'avue-';
       let result = 'input';
-      if (!this.validatenull(component)) result = component;
-      else if (type === 'array') result = 'array';
+      if (component) return component
+      else if (['array', 'img', 'url'].includes(type)) result = 'array';
       else if (type === 'select') result = 'select';
       else if (type === 'radio') result = 'radio';
       else if (type === 'checkbox') result = 'checkbox';
@@ -118,16 +59,16 @@ export default {
       else if (type === 'upload') result = 'upload';
       else if (type === 'slider') result = 'slider';
       else if (type === 'dynamic') result = 'dynamic';
-      else if (type === 'icon-select') result = 'icon-select';
-      else if (type === 'color') result = 'color';
+      else if (type === 'icon') result = 'input-icon';
+      else if (type === 'color') result = 'input-color';
+      else if (type === 'map') result = 'input-map';
       return KEY_COMPONENT_NAME + result;
     },
-
     getPlaceholder (item) {
       const label = item.label;
-      if (['select', 'checkbox', 'radio', 'tree', 'color', 'dates', 'date', 'datetime', 'datetimerange', 'daterange', 'week', 'month', 'year'].includes(item.type))
-        return `请选择${label}`;
-      else return `请输入${label}`;
+      if (['select', 'checkbox', 'radio', 'tree', 'color', 'dates', 'date', 'datetime', 'datetimerange', 'daterange', 'week', 'month', 'year', 'map', 'icon'].includes(item.type))
+        return `请选择 ${label}`;
+      else return `请输入 ${label}`;
     },
   }
 }

+ 11 - 2
packages/WidgetFormTable.vue

@@ -8,7 +8,8 @@
                ghost-class="ghost"
                :animation="200"
                handle=".widget-form-table__item"
-               @add="handleWidgetTableAdd($event, column)">
+               @add="handleWidgetTableAdd($event, column)"
+               @end="$emit('change')">
       <template v-if="column.children.column.length > 0">
         <div v-for="(item, tableIndex) in column.children.column"
              :key="tableIndex"
@@ -22,7 +23,8 @@
                              :label="item.label"
                              :align="column.children.align"
                              :header-align="column.children.headerAlign">
-              <widget-form-item :item="item"></widget-form-item>
+              <widget-form-item :item="item"
+                                :params="column.params"></widget-form-item>
               <el-button title="删除"
                          @click.stop="handleWidgetTableDelete(column, tableIndex)"
                          class="widget-table-action-delete"
@@ -105,6 +107,7 @@ export default {
     handleWidgetClear (index) {
       this.data.column[index].children.column = []
       this.selectWidget = this.data.column[index]
+      this.$emit("change")
     },
     handleWidgetDelete (index) {
       if (this.data.column.length - 1 === index) {
@@ -114,6 +117,7 @@ export default {
 
       this.$nextTick(() => {
         this.data.column.splice(index, 1)
+        this.$emit("change")
       })
     },
     handleWidgetCloneTable (index) {
@@ -125,6 +129,7 @@ export default {
       this.data.column.splice(index, 0, cloneData)
       this.$nextTick(() => {
         this.handleSelectWidget(index + 1)
+        this.$emit("change")
       })
     },
     handleWidgetTableAdd (evt, column) {
@@ -143,6 +148,8 @@ export default {
       delete data.icon
       this.$set(column.children.column, newIndex, { ...data })
       this.selectWidget = column.children.column[newIndex]
+
+      this.$emit("change")
     },
     handleWidgetTableSelect (data) {
       this.selectWidget = data
@@ -153,6 +160,7 @@ export default {
       this.$set(column.children.column, column.children.column.length, { ...data })
       this.$nextTick(() => {
         this.selectWidget = column.children.column[column.children.column.length - 1]
+        this.$emit("change")
       })
     },
     handleWidgetTableDelete (column, index) {
@@ -162,6 +170,7 @@ export default {
       } else this.selectWidget = column.children.column[index + 1]
       this.$nextTick(() => {
         column.children.column.splice(index, 1)
+        this.$emit("change")
       })
     },
   },

+ 3 - 6
packages/config/array.vue

@@ -2,12 +2,9 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="尺寸">
       <el-radio-group v-model="data.size"
                       size="mini">
@@ -31,7 +28,7 @@
 export default {
   name: 'config-array',
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -42,7 +39,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 0 - 239
packages/config/cascader.vue

@@ -1,239 +0,0 @@
-<template>
-  <div>
-    <el-form-item label="占位内容">
-      <el-input v-model="data.placeholder"
-                placeholder="占位内容"></el-input>
-    </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
-    <el-form-item label="字典配置"><br>
-      <el-tabs v-model="data.dicOption"
-               stretch
-               @tab-click="handleTabClick">
-        <el-tab-pane label="静态数据"
-                     name="static">
-          <el-tree ref="tree"
-                   :data="data.dicData"
-                   default-expand-all
-                   draggable
-                   node-key="value"
-                   :expand-on-click-node="false">
-            <span class="custom-tree-node"
-                  slot-scope="{ node, data }">
-              <span>{{ node.label }}</span>
-              <span>
-                <el-button type="text"
-                           size="mini"
-                           icon="el-icon-plus"
-                           @click="handleNodeAdd(data)"></el-button>
-                <!--                <el-button class="warning" type="text" size="mini" icon="el-icon-edit"-->
-                <!--                           @click="handleNodeEdit(data)"></el-button>-->
-                <el-button class="danger"
-                           type="text"
-                           size="mini"
-                           icon="el-icon-delete"
-                           @click="handleNodeRemove(node, data)"></el-button>
-              </span>
-            </span>
-          </el-tree>
-          <div style="margin-left: 22px;">
-            <el-button type="text"
-                       @click="handleParentNodeAdd">添加父级
-            </el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="远端数据"
-                     name="remote">
-          网址
-          <el-input v-model="data.dicUrl"
-                    placeholder="远端数据字典网址"></el-input>
-          请求方法
-          <el-select v-model="data.dicMethod"
-                     placeholder="请求方法"
-                     style="width: 100%;">
-            <el-option label="POST"
-                       value="post"></el-option>
-            <el-option label="GET"
-                       value="get"></el-option>
-          </el-select>
-          <p v-if="data.dicMethod == 'post'">
-            请求参数
-            <avue-dynamic v-model="data.dicQuery"
-                          :children="option"></avue-dynamic>
-          </p>
-        </el-tab-pane>
-      </el-tabs>
-    </el-form-item>
-    <el-form-item label="选项分隔符">
-      <el-input v-model="data.separator"
-                placeholder="选项分隔符"></el-input>
-    </el-form-item>
-    <el-form-item label="是否显示选中值的完整路径">
-      <el-switch v-model="data.showAllLevels"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可清空">
-      <el-switch v-model="data.clearable"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可搜索">
-      <el-switch v-model="data.filterable"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否禁用">
-      <el-switch v-model="data.disabled"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可见">
-      <el-switch v-model="data.display"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否必填">
-      <el-switch v-model="data.required"></el-switch>
-    </el-form-item>
-
-    <el-dialog :visible.sync="dialogVisible"
-               :rules="dialogRules"
-               :before-close="beforeClose">
-      <el-form ref="dialogForm"
-               :model="dialogForm"
-               label-width="80px">
-        <el-form-item label="label">
-          <el-input v-model="dialogForm.label"
-                    placeholder="label"></el-input>
-        </el-form-item>
-        <el-form-item label="value">
-          <el-input v-model="dialogForm.value"
-                    placeholder="value"
-                    :type="this.dialogInputType">
-            <el-select v-model="dialogInputType"
-                       slot="append"
-                       placeholder="数据类型"
-                       style="width: 100px">
-              <el-option label="String"
-                         value="text"></el-option>
-              <el-option label="Number"
-                         value="number"></el-option>
-            </el-select>
-          </el-input>
-        </el-form-item>
-      </el-form>
-      <span slot="footer"
-            class="dialog-footer">
-        <el-button @click="dialogVisible = false">取 消</el-button>
-        <el-button type="primary"
-                   @click="handleDialogAdd"
-                   v-if="dialogStatus == 'add'">确 定</el-button>
-        <!--        <el-button type="primary" @click="handleDialogUpdate" v-else>确 定</el-button>-->
-      </span>
-    </el-dialog>
-  </div>
-</template>
-<script>
-
-export default {
-  name: "config-cascader",
-  props: ['data'],
-  data () {
-    return {
-      validator: {
-        type: null,
-        required: null,
-        pattern: null,
-        length: null
-      },
-      dialogForm: {},
-      dialogVisible: false,
-      dialogRules: {
-        label: { required: true, message: '请输入label' },
-        value: { required: true, message: '请输入value' },
-      },
-      dialogStatus: 'add',
-      selectData: undefined,
-      dialogInputType: 'text',
-      option: {
-        column: [{
-          type: 'input',
-          prop: 'key',
-          label: 'key'
-        }, {
-          type: 'input',
-          prop: 'value',
-          label: 'value'
-        }]
-      },
-    }
-  },
-  methods: {
-    generateRule () {
-      const rules = [];
-      Object.keys(this.validator).forEach(key => {
-        if (this.validator[key]) rules.push(this.validator[key])
-      })
-      this.data.rules = rules
-    },
-    handleTabClick ({ name }) {
-      if (name == 'remote' && !this.data.dicQuery) this.data.dicQuery = []
-    },
-    handleParentNodeAdd () {
-      this.selectData = undefined
-      this.dialogStatus = 'add';
-      this.dialogVisible = true;
-    },
-    handleNodeAdd (data) {
-      this.selectData = data;
-      this.dialogStatus = 'add';
-      this.dialogVisible = true;
-    },
-    handleNodeRemove (node, data) {
-      const parent = node.parent;
-      const children = parent.data.children || parent.data;
-      const index = children.findIndex(d => d.id === data.id);
-      children.splice(index, 1);
-    },
-    handleDialogAdd () {
-      this.$refs.dialogForm.validate((valid) => {
-        if (valid) {
-          const { label, value } = this.dialogForm;
-          const node = this.$refs.tree.getNode(value)
-          if (node) this.$message.error("value重复")
-          else {
-            const data = this.selectData
-            const newNode = {
-              label,
-              value: this.dialogInputType == 'number' ? Number(value) : value,
-            }
-            if (data) {
-              if (!data.children) this.$set(data, 'children', [])
-              data.children.push(newNode)
-            } else {
-              this.$set(this.data.dicData, this.data.dicData.length, newNode)
-            }
-            this.beforeClose()
-          }
-        }
-      })
-    },
-    beforeClose () {
-      this.$refs.dialogForm.clearValidate()
-      this.dialogForm = {}
-      this.dialogVisible = false
-    }
-  },
-  watch: {
-    'data.required': function (val) {
-      if (val) this.validator.required = { required: true, message: `请选择${this.data.label}` }
-      else this.validator.required = null
-
-      this.generateRule()
-    }
-  }
-}
-</script>
-<style lang="scss" scoped>
-.custom-tree-node {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 14px;
-  padding-right: 8px;
-}
-</style>

+ 0 - 151
packages/config/checkbox.vue

@@ -1,151 +0,0 @@
-<template>
-  <div>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
-    <el-form-item label="字典配置"><br>
-      <el-tabs v-model="data.dicOption"
-               stretch
-               @tab-click="handleTabClick">
-        <el-tab-pane label="静态数据"
-                     name="static">
-          <draggable tag="ul"
-                     :list="data.dicData"
-                     :group="{ name: 'dic' }"
-                     ghost-class="ghost"
-                     handle=".drag-item">
-            <li v-for="(item, index) in data.dicData"
-                :key="index">
-              <i class="drag-item el-icon-s-operation"
-                 style="font-size: 16px; margin: 0 5px; cursor: move;"></i>
-              <el-input style="margin-right: 5px;"
-                        size="mini"
-                        v-model="item.label"
-                        placeholder="label"></el-input>
-              <el-input size="mini"
-                        v-model="item.value"
-                        placeholder="value"></el-input>
-              <el-button @click="handleRemoveFields(index)"
-                         circle
-                         plain
-                         type="danger"
-                         size="mini"
-                         icon="el-icon-minus"
-                         style="padding: 4px;margin-left: 5px;"></el-button>
-            </li>
-          </draggable>
-          <div style="margin-left: 22px;">
-            <el-button type="text"
-                       @click="handleAddFields">添加列
-            </el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="远端数据"
-                     name="remote">
-          网址
-          <el-input v-model="data.dicUrl"
-                    placeholder="远端数据字典网址"></el-input>
-          请求方法
-          <el-select v-model="data.dicMethod"
-                     placeholder="请求方法"
-                     style="width: 100%;">
-            <el-option label="POST"
-                       value="post"></el-option>
-            <el-option label="GET"
-                       value="get"></el-option>
-          </el-select>
-          <p v-if="data.dicMethod == 'post'">
-            请求参数
-            <avue-dynamic v-model="data.dicQuery"
-                          :children="option"></avue-dynamic>
-          </p>
-        </el-tab-pane>
-      </el-tabs>
-    </el-form-item>
-    <el-form-item label="边框"
-                  v-if="!data.button">
-      <el-switch v-model="data.border"></el-switch>
-    </el-form-item>
-    <el-form-item label="按钮"
-                  v-if="!data.border">
-      <el-switch v-model="data.button"></el-switch>
-    </el-form-item>
-    <el-form-item label="尺寸"
-                  v-if="data.border || data.button">
-      <el-radio-group v-model="data.size"
-                      size="mini">
-        <el-radio-button label="medium">正常</el-radio-button>
-        <el-radio-button label="small">小</el-radio-button>
-        <el-radio-button label="mini">超小</el-radio-button>
-      </el-radio-group>
-    </el-form-item>
-    <el-form-item label="是否禁用">
-      <el-switch v-model="data.disabled"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可见">
-      <el-switch v-model="data.display"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否必填">
-      <el-switch v-model="data.required"></el-switch>
-    </el-form-item>
-  </div>
-</template>
-
-<script>
-import Draggable from 'vuedraggable'
-
-export default {
-  name: "config-checkbox",
-  props: ['data'],
-  components: { Draggable },
-  data () {
-    return {
-      validator: {
-        type: null,
-        required: null,
-        pattern: null,
-        length: null
-      },
-      option: {
-        column: [{
-          type: 'input',
-          prop: 'key',
-          label: 'key'
-        }, {
-          type: 'input',
-          prop: 'value',
-          label: 'value'
-        }]
-      },
-    }
-  },
-  methods: {
-    generateRule () {
-      const rules = [];
-      Object.keys(this.validator).forEach(key => {
-        if (this.validator[key]) rules.push(this.validator[key])
-      })
-      this.data.rules = rules
-    },
-    handleRemoveFields (index) {
-      this.data.dicData.splice(index, 1)
-    },
-    handleAddFields () {
-      const i = Math.ceil(Math.random() * 99999)
-      this.data.dicData.push({ label: `字段${i}`, value: `col_${i}` })
-    },
-    handleTabClick ({ name }) {
-      if (name == 'remote' && !this.data.dicQuery) this.data.dicQuery = []
-    }
-  },
-  watch: {
-    'data.required': function (val) {
-      if (val) this.validator.required = { required: true, message: `请选择${this.data.label}` }
-      else this.validator.required = null
-
-      this.generateRule()
-    }
-  }
-}
-</script>

+ 3 - 2
packages/config/color.vue

@@ -2,6 +2,7 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
     <el-form-item label="是否禁用">
@@ -20,7 +21,7 @@
 export default {
   name: "config-color",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -31,7 +32,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 17 - 9
packages/config/date.vue

@@ -1,36 +1,44 @@
 <template>
   <div>
     <template v-if="data.type.indexOf('range') != -1">
-      <el-form-item label="开始占位内容">
+      <el-form-item label="开始占位内容"
+                    label-width="110px">
         <el-input v-model="data.startPlaceholder"
+                  clearable
                   placeholder="开始占位内容"></el-input>
       </el-form-item>
-      <el-form-item label="结束占位内容">
+      <el-form-item label="结束占位内容"
+                    label-width="110px">
         <el-input v-model="data.endPlaceholder"
+                  clearable
                   placeholder="结束占位内容"></el-input>
       </el-form-item>
     </template>
     <el-form-item label="占位内容"
                   v-else>
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
-    <el-form-item label="显示格式化">
+    <el-form-item label="显示格式化"
+                  label-width="100px">
       <el-input v-model="data.format"
+                clearable
                 placeholder="显示格式化"></el-input>
     </el-form-item>
     <el-form-item label="值格式化">
       <el-input v-model="data.valueFormat"
+                clearable
                 placeholder="值格式化"></el-input>
     </el-form-item>
     <el-form-item label="取消范围联动"
+                  label-width="110px"
                   v-if="['timerange', 'daterange', 'datetimerange'].includes(data.type)">
       <el-switch v-model="data.unlinkPanels"></el-switch>
     </el-form-item>
+    <el-form-item label="是否只读">
+      <el-switch v-model="data.readonly"></el-switch>
+    </el-form-item>
     <el-form-item label="是否禁用">
       <el-switch v-model="data.disabled"></el-switch>
     </el-form-item>
@@ -47,7 +55,7 @@
 export default {
   name: "config-date",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -58,7 +66,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 52 - 5
packages/config/dynamic.vue

@@ -2,7 +2,8 @@
   <div>
     <el-form-item label="对齐方式">
       <el-select v-model="data.children.align"
-                 placeholder="对齐方式">
+                 placeholder="对齐方式"
+                 clearable>
         <el-option label="居左"
                    value="left"></el-option>
         <el-option label="居中"
@@ -11,9 +12,11 @@
                    value="right"></el-option>
       </el-select>
     </el-form-item>
-    <el-form-item label="头部对齐方式">
+    <el-form-item label="头部对齐方式"
+                  label-width="110px">
       <el-select v-model="data.children.headerAlign"
-                 placeholder="对齐方式">
+                 placeholder="对齐方式"
+                 clearable>
         <el-option label="居左"
                    value="left"></el-option>
         <el-option label="居中"
@@ -22,6 +25,20 @@
                    value="right"></el-option>
       </el-select>
     </el-form-item>
+    <el-form-item label="表单格式">
+      <el-select v-model="data.children.type"
+                 placeholder="表单格式"
+                 clearable>
+        <el-option label="表格"
+                   value=""></el-option>
+        <el-option label="表单"
+                   value="form"></el-option>
+      </el-select>
+    </el-form-item>
+    <el-form-item label="序号"
+                  v-if="data.children.type == 'form'">
+      <el-switch v-model="data.children.index"></el-switch>
+    </el-form-item>
     <el-form-item label="添加按钮">
       <el-switch v-model="data.children.addBtn"></el-switch>
     </el-form-item>
@@ -29,7 +46,7 @@
       <el-switch v-model="data.children.delBtn"></el-switch>
     </el-form-item>
     <el-form-item label="是否只读">
-      <el-switch v-model="data.children.viewBtn"></el-switch>
+      <el-switch v-model="data.readonly"></el-switch>
     </el-form-item>
     <el-form-item label="是否禁用">
       <el-switch v-model="data.disabled"></el-switch>
@@ -37,12 +54,42 @@
     <el-form-item label="是否可见">
       <el-switch v-model="data.display"></el-switch>
     </el-form-item>
+    <el-form-item label="是否必填">
+      <el-switch v-model="data.required"></el-switch>
+    </el-form-item>
   </div>
 </template>
 
 <script>
 export default {
   name: "config-dynamic",
-  props: ['data']
+  props: ['data'],
+  data() {
+    return {
+      validator: {
+        type: null,
+        required: null,
+        pattern: null,
+        length: null
+      }
+    }
+  },
+  methods: {
+    generateRule() {
+      const rules = [];
+      Object.keys(this.validator).forEach(key => {
+        if (this.validator[key]) rules.push(this.validator[key])
+      })
+      this.data.rules = rules
+    },
+  },
+  watch: {
+    'data.required': function (val) {
+      if (val) this.validator.required = { required: true, message: `${this.data.label}必须填写` }
+      else this.validator.required = null
+
+      this.generateRule()
+    },
+  }
 }
 </script>

+ 14 - 4
packages/config/group.vue

@@ -1,9 +1,19 @@
 <template>
   <div>
     <el-form-item label="图标">
-      <avue-icon-select v-model="data.icon"
-                        :icon-list="iconList"
-                        placeholder="图标"></avue-icon-select>
+      <avue-input-icon v-model="data.icon"
+                       :icon-list="iconList"
+                       placeholder="图标"></avue-input-icon>
+    </el-form-item>
+    <el-form-item label="开启折叠">
+      <el-switch v-model="data.arrow"></el-switch>
+    </el-form-item>
+    <el-form-item label="默认展开"
+                  v-if="data.arrow">
+      <el-switch v-model="data.collapse"></el-switch>
+    </el-form-item>
+    <el-form-item label="是否可见">
+      <el-switch v-model="data.display"></el-switch>
     </el-form-item>
   </div>
 </template>
@@ -12,7 +22,7 @@
 export default {
   name: "config-group",
   props: ['data'],
-  data () {
+  data() {
     return {
       iconList: [{
         label: 'element-ui',

+ 7 - 7
packages/config/index.js

@@ -1,3 +1,4 @@
+import Custom from './custom.vue'
 import Input from './input.vue'
 import Textarea from './textarea.vue'
 import Number from './number.vue'
@@ -6,10 +7,7 @@ import Switch from './switch.vue'
 import Rate from './rate.vue'
 import Slider from './slider.vue'
 import Color from './color.vue'
-import Radio from './radio.vue'
-import Checkbox from './checkbox.vue'
 import Select from './select.vue'
-import Cascader from './cascader.vue'
 import Tree from './tree.vue'
 import Date from './date.vue'
 import Upload from './upload.vue'
@@ -17,8 +15,11 @@ import UEditor from './ueditor.vue'
 import Map from './map.vue'
 import Group from './group.vue'
 import Array from './array.vue'
+import Title from './title.vue'
+import Event from './event.vue'
 
 const components = [
+  Custom,
   Input,
   Textarea,
   Number,
@@ -27,17 +28,16 @@ const components = [
   Rate,
   Slider,
   Color,
-  Radio,
-  Checkbox,
   Select,
-  Cascader,
   Tree,
   Date,
   UEditor,
   Upload,
   Map,
   Group,
-  Array
+  Array,
+  Title,
+  Event
 ]
 
 const Config = {

+ 13 - 10
packages/config/input.vue

@@ -2,18 +2,17 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="前缀">
       <el-input v-model="data.prepend"
+                clearable
                 placeholder="前缀"></el-input>
     </el-form-item>
     <el-form-item label="后缀">
       <el-input v-model="data.append"
+                clearable
                 placeholder="后缀"></el-input>
     </el-form-item>
     <el-form-item label="最大长度">
@@ -25,16 +24,20 @@
                   v-if="data.type != 'password'">
       <el-switch v-model="data.showWordLimit"></el-switch>
     </el-form-item>
+    <el-form-item label="显示密码"
+                  v-if="data.type == 'password'">
+      <el-switch v-model="data.showPassword"></el-switch>
+    </el-form-item>
     <el-form-item label="是否只读">
       <el-switch v-model="data.readonly"></el-switch>
     </el-form-item>
     <el-form-item label="是否可见">
       <el-switch v-model="data.display"></el-switch>
     </el-form-item>
-    <el-form-item label="校验"><br>
-      是否必填:
+    <el-form-item label="是否必填">
       <el-switch v-model="data.required"></el-switch>
-      <el-input v-model.lazy="data.pattern"
+      <el-input v-if="data.required"
+                v-model.lazy="data.pattern"
                 placeholder="正则表达式"></el-input>
     </el-form-item>
   </div>
@@ -43,8 +46,8 @@
 <script>
 export default {
   name: "config-input",
-  props: ['data'],
-  data () {
+  props: ['data', 'defaultValues'],
+  data() {
     return {
       validator: {
         type: null,
@@ -55,7 +58,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 1 - 1
packages/config/map.vue

@@ -4,7 +4,7 @@
       <a href="https://avuejs.com/doc/plugins/map-plugins"
          target="_blank"
          style="color: #409EFF;">详细文档</a><br>
-      <el-input v-model="data.valueDefault"
+      <el-input v-model="data.value"
                 placeholder="默认值"></el-input>
     </el-form-item>
     <el-form-item label="是否禁用">

+ 17 - 12
packages/config/number.vue

@@ -2,12 +2,9 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="最小值">
       <el-input-number v-model="data.minRows"
                        controls-position="right"
@@ -30,7 +27,13 @@
                        :min="0"
                        :max="10"></el-input-number>
     </el-form-item>
-    <el-form-item label="控制器位置">
+    <el-form-item label="开启控制器"
+                  label-width="100px">
+      <el-switch v-model="data.controls"></el-switch>
+    </el-form-item>
+    <el-form-item label="控制器位置"
+                  label-width="100px"
+                  v-if="data.controls">
       <el-radio v-model="data.controlsPosition"
                 label="">默认
       </el-radio>
@@ -44,10 +47,11 @@
     <el-form-item label="是否可见">
       <el-switch v-model="data.display"></el-switch>
     </el-form-item>
-    <el-form-item label="校验"><br>
-      是否必填:
+    <el-form-item label="是否必填">
       <el-switch v-model="data.required"></el-switch>
-      <el-input v-model.lazy="data.pattern"
+      <el-input v-if="data.required"
+                v-model.lazy="data.pattern"
+                clearable
                 placeholder="正则表达式"></el-input>
     </el-form-item>
   </div>
@@ -57,7 +61,7 @@
 export default {
   name: "config-number",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -68,11 +72,12 @@ export default {
     }
   },
   methods: {
-    generateRule () {
-      this.data.rules.clear()
+    generateRule() {
+      const rules = [];
       Object.keys(this.validator).forEach(key => {
-        if (this.validator[key]) this.data.rules.push(this.validator[key])
+        if (this.validator[key]) rules.push(this.validator[key])
       })
+      this.data.rules = rules
     },
   },
   watch: {

+ 0 - 147
packages/config/radio.vue

@@ -1,147 +0,0 @@
-<template>
-  <div>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
-    <el-form-item label="字典配置"><br>
-      <el-tabs v-model="data.dicOption"
-               stretch
-               @tab-click="handleTabClick">
-        <el-tab-pane label="静态数据"
-                     name="static">
-          <draggable tag="ul"
-                     :list="data.dicData"
-                     :group="{ name: 'dic' }"
-                     ghost-class="ghost"
-                     handle=".drag-item">
-            <li v-for="(item, index) in data.dicData"
-                :key="index">
-              <i class="drag-item el-icon-s-operation"
-                 style="font-size: 16px; margin: 0 5px; cursor: move;"></i>
-              <el-input style="margin-right: 5px;"
-                        size="mini"
-                        v-model="item.label"
-                        placeholder="label"></el-input>
-              <el-input size="mini"
-                        v-model="item.value"
-                        placeholder="value"></el-input>
-              <el-button @click="handleRemoveFields(index)"
-                         circle
-                         plain
-                         type="danger"
-                         size="mini"
-                         icon="el-icon-minus"
-                         style="padding: 4px;margin-left: 5px;"></el-button>
-            </li>
-          </draggable>
-          <div style="margin-left: 22px;">
-            <el-button type="text"
-                       @click="handleAddFields">添加列
-            </el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="远端数据"
-                     name="remote">
-          网址
-          <el-input v-model="data.dicUrl"
-                    placeholder="远端数据字典网址"></el-input>
-          请求方法
-          <el-select v-model="data.dicMethod"
-                     placeholder="请求方法"
-                     style="width: 100%;">
-            <el-option label="POST"
-                       value="post"></el-option>
-            <el-option label="GET"
-                       value="get"></el-option>
-          </el-select>
-          <p v-if="data.dicMethod == 'post'">
-            请求参数
-            <avue-dynamic v-model="data.dicQuery"
-                          :children="option"></avue-dynamic>
-          </p>
-        </el-tab-pane>
-      </el-tabs>
-    </el-form-item>
-    <el-form-item label="边框">
-      <el-switch v-model="data.border"></el-switch>
-    </el-form-item>
-    <el-form-item label="尺寸"
-                  v-if="data.border">
-      <el-radio-group v-model="data.size"
-                      size="mini">
-        <el-radio-button label="medium">正常</el-radio-button>
-        <el-radio-button label="small">小</el-radio-button>
-        <el-radio-button label="mini">超小</el-radio-button>
-      </el-radio-group>
-    </el-form-item>
-    <el-form-item label="是否禁用">
-      <el-switch v-model="data.disabled"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可见">
-      <el-switch v-model="data.display"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否必填">
-      <el-switch v-model="data.required"></el-switch>
-    </el-form-item>
-  </div>
-</template>
-
-<script>
-import Draggable from 'vuedraggable'
-
-
-export default {
-  name: "config-radio",
-  props: ['data'],
-  components: { Draggable },
-  data () {
-    return {
-      validator: {
-        type: null,
-        required: null,
-        pattern: null,
-        length: null
-      },
-      option: {
-        column: [{
-          type: 'input',
-          prop: 'key',
-          label: 'key'
-        }, {
-          type: 'input',
-          prop: 'value',
-          label: 'value'
-        }]
-      },
-    }
-  },
-  methods: {
-    generateRule () {
-      const rules = [];
-      Object.keys(this.validator).forEach(key => {
-        if (this.validator[key]) rules.push(this.validator[key])
-      })
-      this.data.rules = rules
-    },
-    handleRemoveFields (index) {
-      this.data.dicData.splice(index, 1)
-    },
-    handleAddFields () {
-      const i = Math.ceil(Math.random() * 99999)
-      this.data.dicData.push({ label: `字段${i}`, value: `col_${i}` })
-    },
-    handleTabClick ({ name }) {
-      if (name == 'remote' && !this.data.dicQuery) this.data.dicQuery = []
-    }
-  },
-  watch: {
-    'data.required': function (val) {
-      if (val) this.validator.required = { required: true, message: `请选择${this.data.label}` }
-      else this.validator.required = null
-
-      this.generateRule()
-    }
-  }
-}
-</script>

+ 16 - 14
packages/config/rate.vue

@@ -1,20 +1,19 @@
 <template>
   <div>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="最大星数">
       <el-input-number v-model="data.max"
                        controls-position="right"
                        placeholder="最大星数"></el-input-number>
     </el-form-item>
-    <el-form-item label="是否显示文本">
+    <el-form-item label="是否显示文本"
+                  label-width="110px">
       <el-switch v-model="data.showText"></el-switch>
     </el-form-item>
     <el-form-item label="自定义文本"
+                  label-width="110px"
                   v-if="data.showText">
       <el-tag :key="index"
+              size="small"
               v-for="(tag,index) in data.texts"
               closable
               @close="handleTextClose(tag)">{{tag}}
@@ -24,20 +23,23 @@
                 v-model="textValue"
                 size="mini"
                 ref="textTag"
+                clearable
                 @keyup.enter.native="handleTextConfirm"
                 @blur="handleTextConfirm">
       </el-input>
-      <el-button v-else
+      <el-button v-if="!textVisible && data.texts.length < data.max"
                  @click="showTextInput"
                  size="mini"
                  icon="el-icon-plus"
                  circle
                  style="margin-left: 5px;"></el-button>
     </el-form-item>
-    <el-form-item label="自定义颜色">
+    <el-form-item label="自定义颜色"
+                  label-width="110px">
       <el-tag :key="index"
               v-for="(tag,index) in data.colors"
               closable
+              size="small"
               @close="handleColorClose(tag)"
               :style="{color: tag}">{{tag}}
       </el-tag>
@@ -76,7 +78,7 @@
 export default {
   name: "config-rate",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -93,31 +95,31 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])
       })
       this.data.rules = rules
     },
-    handleTextClose (tag) {
+    handleTextClose(tag) {
       this.data.texts.splice(this.data.texts.indexOf(tag), 1);
     },
-    showTextInput () {
+    showTextInput() {
       this.textVisible = true;
       this.$nextTick(() => {
         this.$refs.textTag.$refs.input.focus();
       });
     },
-    handleTextConfirm () {
+    handleTextConfirm() {
       if (this.textValue) this.data.texts.push(this.textValue);
       this.textVisible = false;
       this.textValue = '';
     },
-    handleColorClose (tag) {
+    handleColorClose(tag) {
       this.data.colors.splice(this.data.colors.indexOf(tag), 1);
     },
-    handleColorConfirm () {
+    handleColorConfirm() {
       if (this.colorValue) this.data.colors.push(this.colorValue);
       this.colorValue = '';
     },

+ 177 - 76
packages/config/select.vue

@@ -1,89 +1,190 @@
 <template>
   <div>
-    <el-form-item label="占位内容">
+    <el-form-item v-if="data.type == 'select'"
+                  label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">字典配置:</label>
+      <div class="el-form-item__content">
+        <el-tabs v-model="data.dicOption"
+                 stretch
+                 @tab-click="handleTabClick">
+          <el-tab-pane label="静态数据"
+                       name="static">
+            <draggable tag="ul"
+                       :list="data.dicData"
+                       :group="{ name: 'dic' }"
+                       ghost-class="ghost"
+                       handle=".drag-item">
+              <li v-for="(item, index) in data.dicData"
+                  :key="index">
+                <i class="drag-item el-icon-s-operation"
+                   style="font-size: 16px; margin: 0 5px; cursor: move;"></i>
+                <el-input style="margin-right: 5px;"
+                          size="mini"
+                          clearable
+                          v-model="item.label"
+                          placeholder="label"></el-input>
+                <el-input :style="{'margin-right': data.type == 'select'? '5px': '0'}"
+                          size="mini"
+                          clearable
+                          v-model="item.value"
+                          placeholder="value"></el-input>
+                <el-input v-if="data.type == 'select'"
+                          size="mini"
+                          clearable
+                          v-model="item.desc"
+                          placeholder="描述"></el-input>
+                <el-button @click="handleRemoveFields(index)"
+                           circle
+                           plain
+                           type="danger"
+                           size="mini"
+                           icon="el-icon-minus"
+                           style="padding: 4px;margin-left: 5px;"></el-button>
+              </li>
+            </draggable>
+            <div style="margin-left: 22px;">
+              <el-button type="text"
+                         @click="handleAddFields">添加列
+              </el-button>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane label="远端数据"
+                       name="remote">
+            网址
+            <el-input v-model="data.dicUrl"
+                      placeholder="远端数据字典网址"></el-input>
+            远程搜索
+            <el-switch v-model="data.remote"></el-switch><br>
+            请求方法
+            <el-select v-model="data.dicMethod"
+                       placeholder="请求方法"
+                       style="width: 100%;">
+              <el-option label="POST"
+                         value="post"></el-option>
+              <el-option label="GET"
+                         value="get"></el-option>
+            </el-select>
+            <p v-if="data.dicMethod == 'post'">
+              请求参数
+              <avue-dynamic v-model="data.dicQueryConfig"
+                            :children="option"></avue-dynamic>
+            </p>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">字典key配置:</label>
+      <div class="el-form-item__content">
+        <ul>
+          <li v-for="(value, key) in data.props"
+              :key="key">
+            <span style="width: 50px">{{ key }}</span>
+            <el-input size="mini"
+                      v-model="data.props[key]"
+                      clearable
+                      placeholder="key配置"></el-input>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <el-form-item v-if="data.dicOption == 'remote'"
+                  label="重新请求字典(crud)"
+                  label-width="150px">
+      <el-switch v-model="data.dicFlag"></el-switch>
     </el-form-item>
-    <el-form-item label="是否多选">
-      <el-switch v-model="data.multiple"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可拖拽(需引入sortable.js)"
-                  v-if="data.multiple">
-      <el-switch v-model="data.drag"></el-switch>
-    </el-form-item>
-    <el-form-item label="多选数量限制"
-                  v-if="data.multiple">
-      <el-input-number v-model="data.limit"
-                       controls-position="right"
-                       placeholder="多选限制"
-                       :min="0"
-                       :max="data.dicData.length"></el-input-number>
-    </el-form-item>
-    <el-form-item label="字典配置"><br>
-      <el-tabs v-model="data.dicOption"
-               stretch
-               @tab-click="handleTabClick">
-        <el-tab-pane label="静态数据"
-                     name="static">
+    <template v-if="data.type == 'select'">
+      <el-form-item label="是否多选">
+        <el-switch v-model="data.multiple"></el-switch>
+      </el-form-item>
+      <el-form-item label="是否可拖拽"
+                    label-width="110px"
+                    v-if="data.multiple">
+        <template slot="label">
+          <el-link :underline="false"
+                   href="https://avuejs.com/doc/form/form-select-drag"
+                   target="_blank">是否可拖拽 <i class="el-icon-question"></i></el-link>
+        </template>
+        <el-switch v-model="data.drag"></el-switch>
+      </el-form-item>
+      <el-form-item label="多选数量限制"
+                    label-width="110px"
+                    v-if="data.multiple">
+        <el-input-number v-model="data.limit"
+                         controls-position="right"
+                         placeholder="多选限制"
+                         :min="0"
+                         :max="data.dicData.length"></el-input-number>
+      </el-form-item>
+      <div class="el-form-item el-form-item--small el-form--label-top">
+        <label class="el-form-item__label"
+               style="padding: 0;">级联配置:</label>
+        <div class="el-form-item__content">
           <draggable tag="ul"
-                     :list="data.dicData"
-                     :group="{ name: 'dic' }"
+                     :list="data.cascaderItem"
+                     :group="{ name: 'cascaderItem' }"
                      ghost-class="ghost"
                      handle=".drag-item">
-            <li v-for="(item, index) in data.dicData"
+            <li v-for="(item, index) in data.cascaderItem"
                 :key="index">
               <i class="drag-item el-icon-s-operation"
                  style="font-size: 16px; margin: 0 5px; cursor: move;"></i>
-              <el-input style="margin-right: 5px;"
-                        size="mini"
-                        v-model="item.label"
-                        placeholder="label"></el-input>
               <el-input size="mini"
-                        v-model="item.value"
-                        placeholder="value"></el-input>
-              <el-button @click="handleRemoveFields(index)"
+                        v-model="data.cascaderItem[index]"
+                        clearable
+                        placeholder="级联属性值"></el-input>
+              <el-button @click="handleRemoveCascaderItemFields(index)"
                          circle
                          plain
                          type="danger"
                          size="mini"
                          icon="el-icon-minus"
-                         style="padding: 4px;margin-left: 5px;"></el-button>
+                         style="padding: 4px; margin-left: 5px;">
+              </el-button>
             </li>
           </draggable>
           <div style="margin-left: 22px;">
             <el-button type="text"
-                       @click="handleAddFields">添加列
-            </el-button>
+                       @click="handleAddCascaderItemFields">添加列</el-button>
           </div>
-        </el-tab-pane>
-        <el-tab-pane label="远端数据"
-                     name="remote">
-          网址
-          <el-input v-model="data.dicUrl"
-                    placeholder="远端数据字典网址"></el-input>
-          请求方法
-          <el-select v-model="data.dicMethod"
-                     placeholder="请求方法"
-                     style="width: 100%;">
-            <el-option label="POST"
-                       value="post"></el-option>
-            <el-option label="GET"
-                       value="get"></el-option>
-          </el-select>
-          <p v-if="data.dicMethod == 'post'">
-            请求参数
-            <avue-dynamic v-model="data.dicQuery"
-                          :children="option"></avue-dynamic>
-          </p>
-
-        </el-tab-pane>
-      </el-tabs>
-    </el-form-item>
-    <el-form-item label="尺寸">
+        </div>
+      </div>
+      <el-form-item v-if="data.cascaderItem && data.cascaderItem.length > 0"
+                    label="级联默认选中"
+                    label-width="110px">
+        <el-input-number v-model="data.cascaderIndex"
+                         controls-position="right"
+                         placeholder="级联默认选中"
+                         :min="0"></el-input-number>
+      </el-form-item>
+      <el-form-item label="是否可清空"
+                    label-width="110px">
+        <el-switch v-model="data.clearable"></el-switch>
+      </el-form-item>
+      <el-form-item label="是否可搜索"
+                    label-width="110px">
+        <el-switch v-model="data.filterable"></el-switch>
+      </el-form-item>
+    </template>
+    <template v-if="['radio','checkbox'].includes(data.type)">
+      <el-form-item label="边框"
+                    v-if="!data.button">
+        <el-switch v-model="data.border"></el-switch>
+      </el-form-item>
+      <el-form-item label="按钮"
+                    v-if="!data.border">
+        <el-switch v-model="data.button"></el-switch>
+      </el-form-item>
+    </template>
+    <el-form-item label="尺寸"
+                  v-if="data.border || data.button">
       <el-radio-group v-model="data.size"
                       size="mini">
         <el-radio-button label="medium">正常</el-radio-button>
@@ -91,12 +192,6 @@
         <el-radio-button label="mini">超小</el-radio-button>
       </el-radio-group>
     </el-form-item>
-    <el-form-item label="是否可清空">
-      <el-switch v-model="data.clearable"></el-switch>
-    </el-form-item>
-    <el-form-item label="是否可搜索">
-      <el-switch v-model="data.filterable"></el-switch>
-    </el-form-item>
     <el-form-item label="是否禁用">
       <el-switch v-model="data.disabled"></el-switch>
     </el-form-item>
@@ -116,7 +211,7 @@ export default {
   name: "config-select",
   props: ['data'],
   components: { Draggable },
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -138,22 +233,28 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])
       })
       this.data.rules = rules
     },
-    handleRemoveFields (index) {
+    handleRemoveFields(index) {
       this.data.dicData.splice(index, 1)
     },
-    handleAddFields () {
+    handleAddFields() {
       const i = Math.ceil(Math.random() * 99999)
       this.data.dicData.push({ label: `字段${i}`, value: `col_${i}` })
     },
-    handleTabClick ({ name }) {
-      if (name == 'remote' && !this.data.dicQuery) this.data.dicQuery = []
+    handleRemoveCascaderItemFields(index) {
+      this.data.cascaderItem.splice(index, 1)
+    },
+    handleAddCascaderItemFields() {
+      this.data.cascaderItem.push('')
+    },
+    handleTabClick({ name }) {
+      if (name == 'remote' && !this.data.dicQueryConfig) this.data.dicQueryConfig = []
     }
   },
   watch: {
@@ -162,7 +263,7 @@ export default {
       else this.validator.required = null
 
       this.generateRule()
-    }
+    },
   }
 }
 </script>

+ 7 - 9
packages/config/slider.vue

@@ -1,9 +1,5 @@
 <template>
   <div>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="最小值">
       <el-input-number v-model="data.min"
                        controls-position="right"
@@ -24,15 +20,17 @@
                        :min="1"
                        :max="data.max - data.min"></el-input-number>
     </el-form-item>
-    <el-form-item label="显示间隔点">
+    <el-form-item label="显示间隔点"
+                  label-width="100px">
       <el-switch v-model="data.showStops"></el-switch>
     </el-form-item>
-    <el-form-item label="显示输入框">
+    <el-form-item label="显示输入框"
+                  label-width="100px">
       <el-switch v-model="data.showInput"></el-switch>
     </el-form-item>
-    <!--    <el-form-item label="范围滑块">-->
-    <!--      <el-switch v-model="data.range"></el-switch>-->
-    <!--    </el-form-item>-->
+    <el-form-item label="范围">
+      <el-switch v-model="data.range"></el-switch>
+    </el-form-item>
     <el-form-item label="是否禁用">
       <el-switch v-model="data.disabled"></el-switch>
     </el-form-item>

+ 3 - 5
packages/config/switch.vue

@@ -1,9 +1,5 @@
 <template>
   <div>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="自定义">
       <el-button type="text"
                  @click="handleDicClear"
@@ -15,9 +11,11 @@
         <el-input size="mini"
                   v-model="item.label"
                   placeholder="自定义文字"
+                  clearable
                   style="margin-right: 5px"></el-input>
         <el-input size="mini"
                   v-model="item.value"
+                  clearable
                   placeholder="自定义值"></el-input>
       </div>
     </el-form-item>
@@ -35,7 +33,7 @@ export default {
   name: "config-switch",
   props: ['data'],
   methods: {
-    handleDicClear () {
+    handleDicClear() {
       this.data.dicData = [{ label: '', value: '0' }, { label: '', value: '1' }]
     }
   },

+ 9 - 9
packages/config/textarea.vue

@@ -2,12 +2,11 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                type="textarea"
+                autosize
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
     <el-form-item label="最大长度">
       <el-input-number v-model="data.maxlength"
                        controls-position="right"
@@ -34,10 +33,11 @@
     <el-form-item label="是否可见">
       <el-switch v-model="data.display"></el-switch>
     </el-form-item>
-    <el-form-item label="校验"><br>
-      是否必填:
+    <el-form-item label="是否必填">
       <el-switch v-model="data.required"></el-switch>
-      <el-input v-model.lazy="data.pattern"
+      <el-input v-if="data.required"
+                v-model.lazy="data.pattern"
+                clearable
                 placeholder="正则表达式"></el-input>
     </el-form-item>
   </div>
@@ -47,7 +47,7 @@
 export default {
   name: "config-textarea",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -58,7 +58,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 150 - 70
packages/config/tree.vue

@@ -2,73 +2,147 @@
   <div>
     <el-form-item label="占位内容">
       <el-input v-model="data.placeholder"
+                clearable
                 placeholder="占位内容"></el-input>
     </el-form-item>
-    <el-form-item label="默认值">
-      <el-input v-model="data.valueDefault"
-                placeholder="默认值"></el-input>
-    </el-form-item>
-    <el-form-item label="字典配置"><br>
-      <el-tabs v-model="data.dicOption"
-               stretch
-               @tab-click="handleTabClick">
-        <el-tab-pane label="静态数据"
-                     name="static">
-          <el-tree ref="tree"
-                   :data="data.dicData"
-                   default-expand-all
-                   draggable
-                   node-key="value"
-                   :expand-on-click-node="false">
-            <span class="custom-tree-node"
-                  slot-scope="{ node, data }">
-              <span>{{ node.label }}</span>
-              <span>
-                <el-button type="text"
-                           size="mini"
-                           icon="el-icon-plus"
-                           @click="handleNodeAdd(data)"></el-button>
-                <!--                <el-button class="warning" type="text" size="mini" icon="el-icon-edit"-->
-                <!--                           @click="handleNodeEdit(data)"></el-button>-->
-                <el-button class="danger"
-                           type="text"
-                           size="mini"
-                           icon="el-icon-delete"
-                           @click="handleNodeRemove(node, data)"></el-button>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">字典配置:</label>
+      <div class="el-form-item__content">
+        <el-tabs v-model="data.dicOption"
+                 stretch
+                 @tab-click="handleTabClick">
+          <el-tab-pane label="静态数据"
+                       name="static">
+            <el-tree ref="tree"
+                     :data="data.dicData"
+                     default-expand-all
+                     draggable
+                     node-key="value"
+                     :expand-on-click-node="false">
+              <span class="custom-tree-node"
+                    slot-scope="{ node, data }">
+                <span>{{ node.label }}</span>
+                <span>
+                  <el-button type="text"
+                             size="mini"
+                             icon="el-icon-plus"
+                             @click="handleNodeAdd(data)"></el-button>
+                  <!--                <el-button class="warning" type="text" size="mini" icon="el-icon-edit"-->
+                  <!--                           @click="handleNodeEdit(data)"></el-button>-->
+                  <el-button class="danger"
+                             type="text"
+                             size="mini"
+                             icon="el-icon-delete"
+                             @click="handleNodeRemove(node, data)"></el-button>
+                </span>
               </span>
-            </span>
-          </el-tree>
+            </el-tree>
+            <div style="margin-left: 22px;">
+              <el-button type="text"
+                         @click="handleParentNodeAdd">添加父级
+              </el-button>
+            </div>
+          </el-tab-pane>
+          <el-tab-pane label="远端数据"
+                       name="remote">
+            网址
+            <el-input v-model="data.dicUrl"
+                      placeholder="远端数据字典网址"></el-input>
+            远程搜索
+            <el-switch v-model="data.remote"></el-switch><br>
+            请求方法
+            <el-select v-model="data.dicMethod"
+                       placeholder="请求方法"
+                       style="width: 100%;">
+              <el-option label="POST"
+                         value="post"></el-option>
+              <el-option label="GET"
+                         value="get"></el-option>
+            </el-select>
+            <p v-if="data.dicMethod == 'post'">
+              请求参数
+              <avue-dynamic v-model="data.dicQueryConfig"
+                            :children="option"></avue-dynamic>
+            </p>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">字典key配置:</label>
+      <div class="el-form-item__content">
+        <ul>
+          <li v-for="(value, key) in data.props"
+              :key="key">
+            <span style="width: 50px">{{ key }}</span>
+            <el-input size="mini"
+                      v-model="data.props[key]"
+                      placeholder="key配置"></el-input>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <el-form-item v-if="data.dicOption == 'remote'"
+                  label="重新请求字典(crud)">
+      <el-switch v-model="data.dicFlag"></el-switch>
+    </el-form-item>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+        <label class="el-form-item__label"
+               style="padding: 0;">级联配置:</label>
+        <div class="el-form-item__content">
+          <draggable tag="ul"
+                     :list="data.cascaderItem"
+                     :group="{ name: 'cascaderItem' }"
+                     ghost-class="ghost"
+                     handle=".drag-item">
+            <li v-for="(item, index) in data.cascaderItem"
+                :key="index">
+              <i class="drag-item el-icon-s-operation"
+                 style="font-size: 16px; margin: 0 5px; cursor: move;"></i>
+              <el-input size="mini"
+                        v-model="data.cascaderItem[index]"
+                        clearable
+                        placeholder="级联属性值"></el-input>
+              <el-button @click="handleRemoveCascaderItemFields(index)"
+                         circle
+                         plain
+                         type="danger"
+                         size="mini"
+                         icon="el-icon-minus"
+                         style="padding: 4px; margin-left: 5px;">
+              </el-button>
+            </li>
+          </draggable>
           <div style="margin-left: 22px;">
             <el-button type="text"
-                       @click="handleParentNodeAdd">添加父级
-            </el-button>
+                       @click="handleAddCascaderItemFields">添加列</el-button>
           </div>
-        </el-tab-pane>
-        <el-tab-pane label="远端数据"
-                     name="remote">
-          网址
-          <el-input v-model="data.dicUrl"
-                    placeholder="远端数据字典网址"></el-input>
-          请求方法
-          <el-select v-model="data.dicMethod"
-                     placeholder="请求方法"
-                     style="width: 100%;">
-            <el-option label="POST"
-                       value="post"></el-option>
-            <el-option label="GET"
-                       value="get"></el-option>
-          </el-select>
-          <p v-if="data.dicMethod == 'post'">
-            请求参数
-            <avue-dynamic v-model="data.dicQuery"
-                          :children="option"></avue-dynamic>
-          </p>
-        </el-tab-pane>
-      </el-tabs>
-    </el-form-item>
-    <el-form-item label="当有子级时,是否可选择父级">
-      <el-switch v-model="data.parent"></el-switch>
-    </el-form-item>
+        </div>
+      </div>
+    <template v-if="data.type == 'tree'">
+      <el-form-item label="当有子级时,是否可选择父级"
+                    label-width="200px">
+        <el-switch v-model="data.parent"></el-switch>
+      </el-form-item>
+    </template>
+    <template v-if="data.type == 'cascader'">
+      <el-form-item label="选项分隔符"
+                    label-width="100px">
+        <el-input v-model="data.separator"
+                  clearable
+                  placeholder="选项分隔符"></el-input>
+      </el-form-item>
+      <el-form-item label="是否显示选中值的完整路径"
+                    label-width="200px">
+        <el-switch v-model="data.showAllLevels"></el-switch>
+      </el-form-item>
+      <el-form-item label="是否可搜索"
+                    label-width="100px">
+        <el-switch v-model="data.filterable"></el-switch>
+      </el-form-item>
+    </template>
     <el-form-item label="是否多选">
       <el-switch v-model="data.multiple"></el-switch>
     </el-form-item>
@@ -125,7 +199,7 @@
 export default {
   name: "config-tree",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -156,33 +230,39 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    handleRemoveCascaderItemFields(index) {
+      this.data.cascaderItem.splice(index, 1)
+    },
+    handleAddCascaderItemFields() {
+      this.data.cascaderItem.push('')
+    },
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])
       })
       this.data.rules = rules
     },
-    handleTabClick ({ name }) {
-      if (name == 'remote' && !this.data.dicQuery) this.data.dicQuery = []
+    handleTabClick({ name }) {
+      if (name == 'remote' && !this.data.dicQueryConfig) this.data.dicQueryConfig = []
     },
-    handleParentNodeAdd () {
+    handleParentNodeAdd() {
       this.selectData = undefined
       this.dialogStatus = 'add';
       this.dialogVisible = true;
     },
-    handleNodeAdd (data) {
+    handleNodeAdd(data) {
       this.selectData = data;
       this.dialogStatus = 'add';
       this.dialogVisible = true;
     },
-    handleNodeRemove (node, data) {
+    handleNodeRemove(node, data) {
       const parent = node.parent;
       const children = parent.data.children || parent.data;
       const index = children.findIndex(d => d.id === data.id);
       children.splice(index, 1);
     },
-    handleDialogAdd () {
+    handleDialogAdd() {
       this.$refs.dialogForm.validate((valid) => {
         if (valid) {
           const { label, value } = this.dialogForm;
@@ -205,7 +285,7 @@ export default {
         }
       })
     },
-    beforeClose () {
+    beforeClose() {
       this.$refs.dialogForm.clearValidate()
       this.dialogForm = {}
       this.dialogVisible = false

+ 94 - 56
packages/config/ueditor.vue

@@ -1,22 +1,31 @@
 <template>
   <div>
-    <el-form-item label="图片上传地址">
-      <el-input v-model="data.upload.action"
+    <el-form-item label="图片上传地址"
+                  label-width="110px">
+      <el-input v-model="data.options.action"
+                clearable
                 placeholder="图片上传地址"></el-input>
     </el-form-item>
-    <el-form-item label="配置参数">
-      <a href="https://avuejs.com/doc/plugins/ueditor-plugins"
-         target="_blank"
-         style="color: #409EFF;">详细文档</a><br>
-      返回的数据结构层次
-      <el-input v-model="data.upload.props.res"
-                placeholder="返回的数据结构层次"></el-input>
-      返回结构体图片地址字段
-      <el-input v-model="data.upload.props.url"
-                placeholder="返回结构体图片地址字段"></el-input>
-    </el-form-item>
-    <el-form-item label="oss">
-      <el-select v-model="data.upload.oss"
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">
+        <el-link :underline="false"
+                 href="https://avuejs.com/doc/plugins/ueditor-plugins"
+                 target="_blank">参数设置 <i class="el-icon-question"></i></el-link>
+      </label>
+      <div class="el-form-item__content">
+        返回的数据结构层次
+        <el-input v-model="data.options.props.res"
+                  size="small"
+                  placeholder="返回的数据结构层次"></el-input>
+        返回结构体图片地址字段
+        <el-input v-model="data.options.props.url"
+                  size="small"
+                  placeholder="返回结构体图片地址字段"></el-input>
+      </div>
+    </div>
+    <el-form-item label="OSS">
+      <el-select v-model="data.options.oss"
                  placeholder="oss不写则为普通上传"
                  clearable>
         <el-option label="阿里"
@@ -25,43 +34,71 @@
                    value="qiniu"></el-option>
       </el-select>
     </el-form-item>
-    <template v-if="data.upload.oss == 'qiniu'">
-      <el-form-item label="七牛oss配置"><br>
-        AK
-        <el-input v-model="data.upload.qiniu.AK"
-                  placeholder="七牛云的密钥(AK)"></el-input>
-        SK
-        <el-input v-model="data.upload.qiniu.SK"
-                  placeholder="七牛云的密钥(SK)"></el-input>
-        scope
-        <el-input v-model="data.upload.qiniu.scope"
-                  placeholder="七牛云存储的空间名"></el-input>
-        url
-        <el-input v-model="data.upload.qiniu.url"
-                  placeholder="空间的自定义域名"></el-input>
-        deadline
-        <el-input v-model="data.upload.qiniu.deadline"
-                  placeholder="token的过期时间"></el-input>
-      </el-form-item>
+    <template v-if="data.options.oss == 'qiniu'">
+      <div class="el-form-item el-form-item--small el-form--label-top">
+        <label class="el-form-item__label"
+               style="padding: 0;">七牛oss配置:</label>
+        <div class="el-form-item__content">
+          AK:
+          <el-input v-model="data.options.qiniu.AK"
+                    size="small"
+                    clearable
+                    placeholder="七牛云的密钥(AK)"></el-input>
+          SK:
+          <el-input v-model="data.options.qiniu.SK"
+                    size="small"
+                    clearable
+                    placeholder="七牛云的密钥(SK)"></el-input>
+          scope:
+          <el-input v-model="data.options.qiniu.scope"
+                    size="small"
+                    clearable
+                    placeholder="七牛云存储的空间名"></el-input>
+          url:
+          <el-input v-model="data.options.qiniu.url"
+                    size="small"
+                    clearable
+                    placeholder="空间的自定义域名"></el-input>
+          deadline:
+          <el-input v-model="data.options.qiniu.deadline"
+                    size="small"
+                    clearable
+                    placeholder="token的过期时间"></el-input>
+        </div>
+      </div>
     </template>
-    <template v-if="data.upload.oss == 'ali'">
-      <el-form-item label="阿里oss配置"><br>
-        region
-        <el-input v-model="data.upload.ali.region"
-                  placeholder="region"></el-input>
-        endpoint
-        <el-input v-model="data.upload.ali.endpoint"
-                  placeholder="endpoint"></el-input>
-        accessKeyId
-        <el-input v-model="data.upload.ali.accessKeyId"
-                  placeholder="accessKeyId"></el-input>
-        accessKeySecret
-        <el-input v-model="data.upload.ali.accessKeySecret"
-                  placeholder="accessKeySecret"></el-input>
-        bucket
-        <el-input v-model="data.upload.ali.bucket"
-                  placeholder="bucket	"></el-input>
-      </el-form-item>
+    <template v-if="data.options.oss == 'ali'">
+      <div class="el-form-item el-form-item--small el-form--label-top">
+        <label class="el-form-item__label"
+               style="padding: 0;">七牛oss配置:</label>
+        <div class="el-form-item__content">
+          region:
+          <el-input v-model="data.options.ali.region"
+                    size="small"
+                    clearable
+                    placeholder="region"></el-input>
+          endpoint:
+          <el-input v-model="data.options.ali.endpoint"
+                    size="small"
+                    clearable
+                    placeholder="endpoint"></el-input>
+          accessKeyId:
+          <el-input v-model="data.options.ali.accessKeyId"
+                    size="small"
+                    clearable
+                    placeholder="accessKeyId"></el-input>
+          accessKeySecret:
+          <el-input v-model="data.options.ali.accessKeySecret"
+                    size="small"
+                    clearable
+                    placeholder="accessKeySecret"></el-input>
+          bucket:
+          <el-input v-model="data.options.ali.bucket"
+                    size="small"
+                    clearable
+                    placeholder="bucket	"></el-input>
+        </div>
+      </div>
     </template>
     <el-form-item label="是否可见">
       <el-switch v-model="data.display"></el-switch>
@@ -76,7 +113,7 @@
 export default {
   name: "config-ueditor",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -87,7 +124,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])
@@ -101,9 +138,10 @@ export default {
       else this.validator.required = null
       this.generateRule()
     },
-    'data.upload.oss': function (val) {
-      if (val == 'ali') this.data.upload.qiniu = {}
-      else if (val == 'qiniu') this.data.upload.ali = {}
+    'data.options.oss': function (val) {
+      debugger
+      if (val == 'ali') this.data.options.qiniu = {}
+      else if (val == 'qiniu') this.data.options.ali = {}
     }
   }
 }

+ 120 - 88
packages/config/upload.vue

@@ -3,17 +3,22 @@
     <el-form-item label="上传地址"
                   v-if="!data.oss">
       <el-input v-model="data.action"
+                clearable
                 placeholder="上传地址"></el-input>
     </el-form-item>
     <el-form-item label="接受文件类型"
+                  label-width="110px"
                   v-if="!data.oss">
       <el-input v-model="data.accept"
-                placeholder="接受文件类型"></el-input>
-    </el-form-item>
-    <el-form-item label="oss">
-      <a href="https://avuejs.com/doc/form/form-upload-qiniu"
-         target="_blank"
-         style="color: #409EFF;">请先按照相关文档配置avue七牛/阿里配置</a><br>
+                clearable
+                placeholder="接受文件类型,如:image/png,image/jpg"></el-input>
+    </el-form-item>
+    <el-form-item label="OSS">
+      <template slot="label">
+        <el-link :underline="false"
+                 href="https://avuejs.com/doc/form/form-upload-qiniu"
+                 target="_blank">OSS <i class="el-icon-question"></i></el-link>
+      </template>
       <el-select v-model="data.oss"
                  placeholder="oss不写则为普通上传"
                  clearable
@@ -24,10 +29,12 @@
                    value="qiniu"></el-option>
       </el-select>
     </el-form-item>
-    <el-form-item label="是否拖拽上传">
+    <el-form-item label="是否拖拽上传"
+                  label-width="110px">
       <el-switch v-model="data.drag"></el-switch>
     </el-form-item>
     <el-form-item label="文件列表类型"
+                  label-width="110px"
                   v-if="!data.drag">
       <el-select v-model="data.listType"
                  placeholder="文件列表类型"
@@ -42,106 +49,131 @@
                    value="picture"></el-option>
       </el-select>
     </el-form-item>
-    <el-form-item label="参数设置">
-      <a href="https://avuejs.com/doc/form/form-upload"
-         target="_blank"
-         style="color: #409EFF;">详细文档</a><br>
-      数据对象的图片地址
-      <el-input v-model="data.props.label"
-                placeholder="数据对象的图片地址"></el-input>
-      数据对象的图片名称
-      <el-input v-model="data.props.value"
-                placeholder="数据对象的图片名称"></el-input>
-      请求头
-      <avue-dynamic v-model="data.headers"
-                    :children="option"></avue-dynamic>
-      请求体
-      <avue-dynamic v-model="data.data"
-                    :children="option"></avue-dynamic>
-    </el-form-item>
-    <el-form-item label="服务器返回参数设置"><br>
-      上传成功返回结构体的图片地址
-      <el-input v-model="data.propsHttp.url"
-                placeholder="上传成功返回结构体的图片地址"></el-input>
-      上传成功返回结构体的图片的姓名
-      <el-input v-model="data.propsHttp.name"
-                placeholder="上传成功返回结构体的图片的姓名"></el-input>
-      返回结构体的层次
-      <el-input v-model="data.propsHttp.res"
-                placeholder="返回结构体的层次"></el-input>
-      上传文件流时的名称
-      <el-input v-model="data.propsHttp.fileName"
-                placeholder="fileName"></el-input>
-    </el-form-item>
-    <el-form-item label="是否显示已上传文件列表">
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">参数设置:</label>
+      <div class="el-form-item__content">
+        请求头
+        <avue-dynamic v-model="data.headersConfig"
+                      :children="option"></avue-dynamic>
+        请求体
+        <avue-dynamic v-model="data.dataConfig"
+                      :children="option"></avue-dynamic>
+      </div>
+    </div>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">
+        <el-link :underline="false"
+                 href="https://avuejs.com/doc/form/form-upload"
+                 target="_blank">上传参数设置 <i class="el-icon-question"></i></el-link>
+      </label>
+      <div class="el-form-item__content">
+        上传成功返回结构体的图片地址
+        <el-input v-model="data.propsHttp.url"
+                  size="small"
+                  placeholder="上传成功返回结构体的图片地址"></el-input>
+        上传成功返回结构体的图片的姓名
+        <el-input v-model="data.propsHttp.name"
+                  size="small"
+                  placeholder="上传成功返回结构体的图片的姓名"></el-input>
+        返回结构体的层次
+        <el-input v-model="data.propsHttp.res"
+                  size="small"
+                  placeholder="返回结构体的层次"></el-input>
+        文件名称
+        <el-input v-model="data.propsHttp.fileName"
+                  size="small"
+                  placeholder="文件名称,默认file"></el-input>
+      </div>
+    </div>
+    <el-form-item label="是否显示已上传文件列表"
+                  label-width="180px">
       <el-switch v-model="data.showFileList"></el-switch>
     </el-form-item>
-    <el-form-item label="文件大小限制(字节)">
-      <el-input-number v-model="data.filesize"
+    <el-form-item label="文件大小">
+      <el-input-number v-model="data.fileSize"
                        controls-position="right"
                        placeholder="文件大小限制(字节)"
                        :min="0"
                        style="width: 100%;"></el-input-number>
     </el-form-item>
-    <el-form-item label="上传限制提示">
+    <el-form-item label="上传限制提示"
+                  label-width="110px">
       <el-input v-model="data.tip"
+                clearable
                 placeholder="上传限制提示"></el-input>
     </el-form-item>
-    <el-form-item label="上传中提示">
+    <el-form-item label="上传中提示"
+                  label-width="110px">
       <el-input v-model="data.loadText"
+                clearable
                 placeholder="上传中提示"></el-input>
     </el-form-item>
-    <el-form-item label="是否多文件上传">
+    <el-form-item label="是否多文件上传"
+                  label-width="130px">
       <el-switch v-model="data.multiple"></el-switch>
     </el-form-item>
-    <el-form-item label="多文件上传数量限制">
+    <el-form-item v-if="data.multiple"
+                  label="多文件数量限制"
+                  label-width="130px">
       <el-input-number v-model="data.limit"
                        controls-position="right"
                        placeholder="多文件上传数量限制"
                        :min="1"
                        style="width: 100%;"></el-input-number>
     </el-form-item>
-    <el-form-item label="图片水印设置"><br>
-      水印文字
-      <el-input v-model="data.canvasOption.text"
-                placeholder="水印文字"></el-input>
-      字体类型
-      <el-input v-model="data.canvasOption.fontFamily"
-                placeholder="字体类型"></el-input>
-      字体颜色
-      <avue-color placeholder="字体颜色"></avue-color>
-      字体大小
-      <el-input-number v-model="data.canvasOption.fontSize"
-                       controls-position="right"
-                       placeholder="字体大小"
-                       style="width: 100%;"></el-input-number>
-      文字的透明度
-      <el-input-number v-model="data.canvasOption.opacity"
-                       controls-position="right"
-                       placeholder="文字的透明度"
-                       :step="10"
-                       :min="10"
-                       :max="100"
-                       style="width: 100%;"></el-input-number>
-      文字距离图片底部的距离<br>
-      <el-input-number v-model="data.canvasOption.bottom"
-                       controls-position="right"
-                       placeholder="文字距离图片底部的距离"
-                       style="width: 100%;"></el-input-number>
-      文字距离图片右边的距离<br>
-      <el-input-number v-model="data.canvasOption.right"
-                       controls-position="right"
-                       placeholder="文字距离图片右边的距离"
-                       style="width: 100%;"></el-input-number>
-      压缩图片比率<br>
-      <el-input-number v-model="data.canvasOption.ratio"
-                       controls-position="right"
-                       placeholder="压缩图片比率"
-                       :step="0.1"
-                       :min="0"
-                       :max="1"
-                       style="width: 100%;"></el-input-number>
-    </el-form-item>
+    <div class="el-form-item el-form-item--small el-form--label-top">
+      <label class="el-form-item__label"
+             style="padding: 0;">水印设置:</label>
+      <div class="el-form-item__content">
+        水印文字
+        <el-input v-model="data.canvasOption.text"
+                  size="small"
+                  clearable
+                  placeholder="水印文字"></el-input>
+        字体类型
+        <el-input v-model="data.canvasOption.fontFamily"
+                  size="small"
+                  clearable
+                  placeholder="字体类型"></el-input>
+        字体颜色
+        <avue-input-color v-model="data.canvasOption.color"
+                          size="small"
+                          placeholder="字体颜色"></avue-input-color>
+        字体大小
+        <el-input-number v-model="data.canvasOption.fontSize"
+                         size="small"
+                         controls-position="right"
+                         placeholder="字体大小"></el-input-number>
+        文字的透明度
+        <el-input-number v-model="data.canvasOption.opacity"
+                         size="small"
+                         controls-position="right"
+                         placeholder="文字的透明度"
+                         :step="10"
+                         :min="10"
+                         :max="100"></el-input-number>
+        文字距离图片底部的距离<br>
+        <el-input-number v-model="data.canvasOption.bottom"
+                         controls-position="right"
+                         placeholder="文字距离图片底部的距离"
+                         size="small"></el-input-number>
+        文字距离图片右边的距离<br>
+        <el-input-number v-model="data.canvasOption.right"
+                         controls-position="right"
+                         placeholder="文字距离图片右边的距离"
+                         size="small"></el-input-number>
+        压缩图片比率<br>
+        <el-input-number v-model="data.canvasOption.ratio"
+                         controls-position="right"
+                         placeholder="压缩图片比率"
+                         :step="0.1"
+                         :min="0"
+                         :max="1"
+                         size="small"></el-input-number>
+      </div>
+    </div>
     <el-form-item label="是否禁用">
       <el-switch v-model="data.disabled"></el-switch>
     </el-form-item>
@@ -158,7 +190,7 @@
 export default {
   name: "config-upload",
   props: ['data'],
-  data () {
+  data() {
     return {
       validator: {
         type: null,
@@ -180,7 +212,7 @@ export default {
     }
   },
   methods: {
-    generateRule () {
+    generateRule() {
       const rules = [];
       Object.keys(this.validator).forEach(key => {
         if (this.validator[key]) rules.push(this.validator[key])

+ 99 - 37
packages/fieldsConfig.js

@@ -6,6 +6,8 @@ export default [
       label: '分组',
       icon: 'icon-group',
       display: true,
+      arrow: false,
+      collapse: true,
       children: {
         column: []
       }
@@ -18,10 +20,23 @@ export default [
       children: {
         align: 'center',
         headerAlign: 'center',
+        index: false,
         addBtn: true,
         delBtn: true,
         column: []
       }
+    }, {
+      type: 'title',
+      icon: 'icon-title',
+      span: 24,
+      display: true,
+      styles: {
+        fontSize: '18px',
+        color: '#000'
+      },
+      label: '标题',
+      labelWidth: '0px',
+      value: '标题',
     }]
   },
   {
@@ -48,8 +63,9 @@ export default [
       type: 'number',
       label: '计数器',
       icon: 'icon-number',
+      controls: true,
       span: 24,
-      display: true
+      display: true,
     }, {
       type: 'url',
       label: '超链接',
@@ -68,6 +84,13 @@ export default [
       icon: 'icon-img',
       span: 24,
       display: true,
+    }, {
+      type: 'map',
+      component: 'avue-input-map',
+      label: '地图选择器',
+      icon: 'icon-map',
+      span: 24,
+      display: true
     }]
   },
   {
@@ -77,37 +100,51 @@ export default [
       label: '单选框组',
       icon: 'icon-radio',
       dicData: [
-        { label: '选项一', value: 0 },
-        { label: '选项二', value: 1 },
-        { label: '选项三', value: 2 },
+        { label: '选项一', value: '0' },
+        { label: '选项二', value: '1' },
+        { label: '选项三', value: '2' },
       ],
       span: 24,
       display: true,
-      dicOption: 'static'
+      dicOption: 'static',
+      props: {
+        label: 'label',
+        value: 'value'
+      }
     }, {
       type: 'checkbox',
       label: '多选框组',
       icon: 'icon-checkbox',
       dicData: [
-        { label: '选项一', value: 0 },
-        { label: '选项二', value: 1 },
-        { label: '选项三', value: 2 },
+        { label: '选项一', value: '0' },
+        { label: '选项二', value: '1' },
+        { label: '选项三', value: '2' },
       ],
       span: 24,
       display: true,
-      dicOption: 'static'
+      dicOption: 'static',
+      props: {
+        label: 'label',
+        value: 'value'
+      }
     }, {
       type: 'select',
       label: '下拉选择器',
       icon: 'icon-select',
       dicData: [
-        { label: '选项一', value: 0 },
-        { label: '选项二', value: 1 },
-        { label: '选项三', value: 2 },
+        { label: '选项一', value: '0' },
+        { label: '选项二', value: '1' },
+        { label: '选项三', value: '2' },
       ],
+      // 用于做级联配置
+      cascaderItem: [],
       span: 24,
       display: true,
-      dicOption: 'static'
+      dicOption: 'static',
+      props: {
+        label: 'label',
+        value: 'value'
+      }
     }, {
       type: 'cascader',
       label: '级联选择器',
@@ -126,12 +163,19 @@ export default [
             value: 12,
           }]
         },
-        { label: '选项二', value: 1 },
-        { label: '选项三', value: 2 },
+        { label: '选项二', value: '1' },
+        { label: '选项三', value: '2' },
       ],
+      cascaderIndex: 1,
+      // 用于做级联配置
+      cascaderItem: [],
       showAllLevels: true,
       dicOption: 'static',
-      separator: "/"
+      separator: "/",
+      props: {
+        label: 'label',
+        value: 'value'
+      }
     }, {
       type: 'tree',
       label: '树形选择器',
@@ -145,16 +189,22 @@ export default [
           value: 0,
           children: [{
             label: '选项1-1',
-            value: 11,
+            value: '11',
           }, {
             label: '选项1-2',
-            value: 12,
+            value: '12',
           }]
         },
-        { label: '选项二', value: 1 },
-        { label: '选项三', value: 2 },
+        { label: '选项二', value: '1' },
+        { label: '选项三', value: '2' },
       ],
-      parent: true
+      parent: true,
+      // 用于做级联配置
+      cascaderItem: [],
+      props: {
+        label: 'label',
+        value: 'value'
+      }
     }]
   },
   {
@@ -168,11 +218,11 @@ export default [
       showFileList: true,
       multiple: true,
       limit: 10,
-      props: {},
+      // props: {},
       propsHttp: {},
       canvasOption: {},
-      headers: [],
-      data: []
+      headersConfig: [],
+      dataConfig: []
     }],
   },
   {
@@ -251,10 +301,10 @@ export default [
     }],
   },
   {
-    title: '插件字段(请自行引入avue插件)',
+    title: '插件字段(需单独引入依赖)',
     list: [{
       type: 'ueditor',
-      component: 'ueditor',
+      component: 'avue-ueditor',
       label: '富文本',
       icon: 'icon-richtext',
       span: 24,
@@ -265,25 +315,37 @@ export default [
         props: {},
         ali: {},
         qiniu: {}
-      }
-    }, {
-      type: 'map',
-      component: 'map',
-      label: '坐标拾取器',
-      icon: 'icon-map',
-      span: 24,
-      display: true,
-    }],
+      },
+    },],
   },
   {
     title: '其他字段',
     list: [{
+      type: 'icon',
+      label: '图标',
+      component: 'avue-input-icon',
+      icon: 'icon-icon',
+      span: 24,
+      display: true,
+      params: {
+        iconList: [{
+          label: '基本图标',
+          list: ['el-icon-info', 'el-icon-error', 'el-icon-error', 'el-icon-success', 'el-icon-warning', 'el-icon-question']
+        }, {
+          label: '方向图标',
+          list: ['el-icon-info', 'el-icon-back', 'el-icon-arrow-left', 'el-icon-arrow-down', 'el-icon-arrow-right', 'el-icon-arrow-up']
+        }, {
+          label: '符号图标',
+          list: ['el-icon-plus', 'el-icon-minus', 'el-icon-close', 'el-icon-check']
+        }]
+      }
+    }, {
       type: 'switch',
       label: '开关',
       icon: 'icon-switch',
       span: 24,
       display: true,
-      valueDefault: 0,
+      value: '0',
       dicData: [{ label: '', value: '0' }, { label: '', value: '1' }]
     }, {
       type: 'rate',
@@ -292,7 +354,7 @@ export default [
       span: 24,
       display: true,
       max: 5,
-      valueDefault: 0,
+      value: 0,
       texts: ['极差', '失望', '一般', '满意', '惊喜'],
       colors: ['#99A9BF', '#F7BA2A', '#FF9900'],
     }, {

+ 414 - 246
packages/index.vue

@@ -4,42 +4,89 @@
       <!-- 左侧字段 -->
       <el-aside :width="leftWidth">
         <div class="fields-list">
+          <template v-if="customFields && customFields.length > 0">
+            <template v-if="customFields[0].title && customFields[0].list && customFields[0].list.length > 0">
+              <template v-for="(field, index) in customFields">
+                <div class="field-title"
+                     :key="'f_' + index">{{field.title}}</div>
+                <draggable tag="ul"
+                           :list="field.list"
+                           :group="{ name: 'form', pull: 'clone', put: false }"
+                           ghost-class="ghost"
+                           :sort="false"
+                           :key="'d_' + index">
+                  <template v-for="(item, cIndex) in field.list">
+                    <li class="field-label"
+                        :key="'c_' + cIndex">
+                      <a @click="handleFieldClick(item)">
+                        <i class="icon iconfont"
+                           :class="item.icon"></i>
+                        <span>{{item.title || item.label}}</span>
+                      </a>
+                    </li>
+                  </template>
+                </draggable>
+              </template>
+            </template>
+            <template v-else>
+              <el-link class="field-title"
+                       :underline="false"
+                       href="https://github.com/sscfaith/avue-form-design/blob/master/CHANGELOG.md#2020-09-22"
+                       target="_blank">自定义字段 <i class="el-icon-question"></i></el-link>
+              <draggable tag="ul"
+                         :list="customFields"
+                         :group="{ name: 'form', pull: 'clone', put: false }"
+                         ghost-class="ghost"
+                         :sort="false">
+                <template v-for="(item, index) in customFields">
+                  <el-tooltip v-if="item.tips"
+                              effect="dark"
+                              :content="item.tips"
+                              :key="index">
+                    <li class="field-label"
+                        :key="index">
+                      <a style="padding: 0 5px;"
+                         @click="handleFieldClick(item)">
+                        <i :class="item.icon"></i>
+                        <span style="margin-left: 5px;">{{item.title || item.label}}</span>
+                      </a>
+                    </li>
+                  </el-tooltip>
+                  <li v-else
+                      class="field-label"
+                      :key="index">
+                    <a style="padding: 0 5px;"
+                       @click="handleFieldClick(item)">
+                      <i :class="item.icon"></i>
+                      <span style="margin-left: 5px;">{{item.title || item.label}}</span>
+                    </a>
+                  </li>
+                </template>
+              </draggable>
+            </template>
+          </template>
           <div v-for="(field, index) in fields"
                :key="index">
-            <div v-if="!field.disabled">
+            <template v-if="field.list.find(f => includeFields.includes(f.type))">
               <div class="field-title">{{field.title}}</div>
               <draggable tag="ul"
                          :list="field.list"
                          :group="{ name: 'form', pull: 'clone', put: false }"
                          ghost-class="ghost"
                          :sort="false">
-                <li class="field-label"
-                    v-for="(item, index) in field.list"
-                    :key="index">
-                  <a>
-                    <i class="icon iconfont"
-                       :class="item.icon"></i>
-                    <span>{{item.label}}</span>
-                  </a>
-                </li>
+                <template v-for="(item, cIndex) in field.list">
+                  <li class="field-label"
+                      v-if="includeFields.includes(item.type)"
+                      :key="'c_' + cIndex">
+                    <a @click="handleFieldClick(item)">
+                      <i class="icon iconfont"
+                         :class="item.icon"></i>
+                      <span>{{item.title || item.label}}</span>
+                    </a>
+                  </li>
+                </template>
               </draggable>
-            </div>
-            <div v-else>
-              <div class="field-title">{{field.title}}
-                <span class="danger">(开发中)</span>
-              </div>
-              <ul>
-                <li class="field-label-disabled"
-                    v-for="(item, index) in field.list"
-                    :key="index">
-                  <a>
-                    <i class="icon iconfont"
-                       :class="item.icon"></i>
-                    <span>{{item.label}}</span>
-                  </a>
-                </li>
-              </ul>
-            </div>
+            </template>
           </div>
         </div>
       </el-aside>
@@ -47,43 +94,76 @@
       <el-container class="widget-container"
                     direction="vertical">
         <el-header class="widget-container-header">
-          <el-button type="text"
-                     size="medium"
-                     icon="el-icon-document"
-                     @click="handleAvueDoc">Avue文档</el-button>
-          <el-button type="text"
-                     size="medium"
-                     icon="el-icon-upload2"
-                     @click="importJsonVisible = true">导入JSON</el-button>
-          <el-button type="text"
-                     size="medium"
-                     icon="el-icon-download"
-                     @click="handleGenerateJson">生成JSON</el-button>
-          <el-button type="text"
-                     size="medium"
-                     icon="el-icon-view"
-                     @click="handlePreview">预览</el-button>
-          <el-button class="danger"
-                     type="text"
-                     size="medium"
-                     icon="el-icon-delete"
-                     @click="handleClear">清空</el-button>
+          <div>
+            <template v-if="undoRedo">
+              <el-button type="text"
+                         size="medium"
+                         icon="el-icon-refresh-left"
+                         :disabled="historySteps.index == 0"
+                         @click="widgetForm = handleUndo()">撤销</el-button>
+              <el-button type="text"
+                         size="medium"
+                         icon="el-icon-refresh-right"
+                         :disabled="historySteps.index == historySteps.steps.length - 1"
+                         @click="widgetForm = handleRedo()">重做</el-button>
+            </template>
+          </div>
+          <div style="display: flex; align-items: center;">
+            <iframe src="https://ghbtns.com/github-btn.html?user=sscfaith&repo=avue-form-design&type=star&count=true"
+                    frameborder="0"
+                    scrolling="0"
+                    width="100"
+                    height="20"
+                    title="GitHub"
+                    style="margin-left: 10px;"
+                    v-if="showGithubStar"></iframe>
+            <slot name="toolbar-left"></slot>
+            <el-button v-if="toolbar.includes('avue-doc')"
+                       type="text"
+                       size="medium"
+                       icon="el-icon-document"
+                       @click="handleAvueDoc">Avue文档</el-button>
+            <el-button v-if="toolbar.includes('import')"
+                       type="text"
+                       size="medium"
+                       icon="el-icon-upload2"
+                       @click="importJsonVisible = true">导入JSON</el-button>
+            <el-button v-if="toolbar.includes('generate')"
+                       type="text"
+                       size="medium"
+                       icon="el-icon-download"
+                       @click="handleGenerateJson">生成JSON</el-button>
+            <el-button v-if="toolbar.includes('preview')"
+                       type="text"
+                       size="medium"
+                       icon="el-icon-view"
+                       @click="handlePreview">预览</el-button>
+            <el-button v-if="toolbar.includes('clear')"
+                       class="danger"
+                       type="text"
+                       size="medium"
+                       icon="el-icon-delete"
+                       @click="handleClear">清空</el-button>
+            <slot name="toolbar"></slot>
+          </div>
         </el-header>
         <el-main :style="{background: widgetForm.column.length == 0 ? `url(${widgetEmpty}) no-repeat 50%`: ''}">
           <widget-form ref="widgetForm"
                        :data="widgetForm"
-                       :select.sync="widgetFormSelect"></widget-form>
+                       :select.sync="widgetFormSelect"
+                       @change="handleHistoryChange(widgetForm)"></widget-form>
         </el-main>
       </el-container>
       <!-- 右侧配置 -->
       <el-aside class="widget-config-container"
-                :width="asideRightWidth">
+                :width="rightWidth">
         <el-tabs v-model="configTab"
                  stretch>
           <el-tab-pane label="字段属性"
                        name="widget"
                        style="padding: 0 10px;">
-            <widget-config :data="widgetFormSelect"></widget-config>
+            <widget-config :data="widgetFormSelect"
+                           :default-values="defaultValues"></widget-config>
           </el-tab-pane>
           <el-tab-pane label="表单属性"
                        name="form"
@@ -98,9 +178,11 @@
       <el-drawer title="导入JSON"
                  :visible.sync="importJsonVisible"
                  size="50%"
+                 append-to-body
                  destroy-on-close>
-        <v-json-editor v-model="importJson"
-                       height="82vh"></v-json-editor>
+        <monaco-editor v-model="importJson"
+                       keyIndex="import"
+                       height="82%"></monaco-editor>
         <div class="drawer-foot">
           <el-button size="medium"
                      type="primary"
@@ -114,57 +196,69 @@
       <el-drawer title="生成JSON"
                  :visible.sync="generateJsonVisible"
                  size="50%"
+                 append-to-body
                  destroy-on-close>
-        <v-json-editor v-model="widgetFormPreview"
-                       height="82vh"></v-json-editor>
+        <monaco-editor v-model="option"
+                       keyIndex="generate"
+                       height="82%"
+                       :read-only="true"></monaco-editor>
         <div class="drawer-foot">
           <el-button size="medium"
                      type="primary"
                      @click="handleGenerate">生成</el-button>
+
           <el-popover placement="top"
                       trigger="hover"
-                      popper-class="popper-bo"
-                      width="250px">
+                      width="350px">
+            <el-form v-model="configOption"
+                     style="padding: 0 20px"
+                     label-suffix=":"
+                     label-width="180px"
+                     label-position="left">
+              <el-form-item label="类型">
+                <el-popover placement="top-start"
+                            trigger="hover"
+                            content="复制json对象"
+                            style="margin-right: 15px;">
+                  <el-radio slot="reference"
+                            v-model="configOption.generateType"
+                            label="json">json</el-radio>
+                </el-popover>
+                <el-popover placement="top-start"
+                            trigger="hover"
+                            content="复制string字符串,可直接用于后端保存无需再次处理。">
+                  <el-radio slot="reference"
+                            v-model="configOption.generateType"
+                            label="string">string</el-radio>
+                </el-popover>
+              </el-form-item>
+              <el-form-item label="缩进长度-空格数量">
+                <el-slider v-model="configOption.space"
+                           show-stops
+                           :marks="{ 1: '1', 2: '2', 3: '3', 4: '4' }"
+                           :min="1"
+                           :max="4"
+                           :step="1"></el-slider>
+              </el-form-item>
+              <el-form-item label="引号类型">
+                <el-switch v-model="configOption.quoteType"
+                           active-value="single"
+                           inactive-value="double"
+                           active-text="单引号"
+                           inactive-text="双引号"></el-switch>
+              </el-form-item>
+              <el-form-item label="移除key的引号">
+                <el-switch v-model="configOption.dropQuotesOnKeys"></el-switch>
+              </el-form-item>
+              <el-form-item label="移除数字字符串的引号">
+                <el-switch v-model="configOption.dropQuotesOnNumbers"></el-switch>
+              </el-form-item>
+            </el-form>
             <el-button size="medium"
                        type="primary"
-                       slot="reference"
                        @click="handleCopy"
+                       slot="reference"
                        style="margin-left: 10px;">复制</el-button>
-            <div>
-              <el-form label-width="180px"
-                       label-position="left">
-                <el-alert :closable="false">
-                  在没有开启美化的情况下,当前编辑器内可见的文本,就是复制得到的内容。<br>
-                  如有需要,您可以开启美化,然后选取适合自己的美化配置。
-                  <a href="https://www.npmjs.com/package/csvjson-json_beautifier"
-                     target="_blank">参考资料</a>
-                </el-alert>
-                <el-form-item label="是否开启美化">
-                  <el-switch v-model="beautifierOptions.enabled" />
-                </el-form-item>
-                <el-form-item label="缩进长度-空格数量">
-                  <el-slider v-model="beautifierOptions.space"
-                             show-stops
-                             :marks="{ 1: '1', 2: '2', 3: '3', 4: '4' }"
-                             :min="1"
-                             :max="4"
-                             :step="1"></el-slider>
-                </el-form-item>
-                <el-form-item label="引号类型">
-                  <el-switch v-model="beautifierOptions.quoteType"
-                             active-value="single"
-                             inactive-value="double"
-                             active-text="单引号"
-                             inactive-text="双引号"></el-switch>
-                </el-form-item>
-                <el-form-item label="移除key的引号">
-                  <el-switch v-model="beautifierOptions.dropQuotesOnKeys"></el-switch>
-                </el-form-item>
-                <el-form-item label="移除数字字符串的引号">
-                  <el-switch v-model="beautifierOptions.dropQuotesOnNumbers"></el-switch>
-                </el-form-item>
-              </el-form>
-            </div>
           </el-popover>
         </div>
       </el-drawer>
@@ -172,12 +266,14 @@
       <el-drawer title="预览"
                  :visible.sync="previewVisible"
                  size="60%"
+                 append-to-body
                  :before-close="handleBeforeClose">
         <avue-form v-if="previewVisible"
                    ref="form"
                    class="preview-form"
-                   :option="widgetFormPreview"
-                   v-model="widgetModels"></avue-form>
+                   :option="option"
+                   v-model="form"
+                   @submit="handlePreviewSubmit"></avue-form>
         <div class="drawer-foot">
           <el-button size="medium"
                      type="primary"
@@ -192,21 +288,25 @@
 </template>
 
 <script>
-import Draggable from 'vuedraggable'
-import VJsonEditor from 'v-jsoneditor'
 import fields from './fieldsConfig.js'
+import beautifier from './utils/json-beautifier'
+import MonacoEditor from './utils/monaco-editor'
+import widgetEmpty from './assets/widget-empty.png'
+import history from './mixins/history'
+
+import Draggable from 'vuedraggable'
+
 import WidgetForm from './WidgetForm'
 import FormConfig from './FormConfig'
 import WidgetConfig from './WidgetConfig'
-import widgetEmpty from './assets/widget-empty.png'
-import beautifier from 'csvjson-json_beautifier'
 
 export default {
   name: "FormDesign",
-  components: { Draggable, VJsonEditor, WidgetForm, FormConfig, WidgetConfig },
+  components: { Draggable, MonacoEditor, WidgetForm, FormConfig, WidgetConfig },
+  mixins: [history],
   props: {
     options: {
-      type: Object,
+      type: [Object, String],
       default: () => {
         return {
           column: []
@@ -224,29 +324,53 @@ export default {
     asideRightWidth: {
       type: [String, Number],
       default: '380px'
-    }
-  },
-  watch: {
-    widgetForm: {
-      handler (val) {
-        if (this.storage) {
-          if (val.column && val.column.length > 0) localStorage.setItem('avue-form', JSON.stringify(val))
-          else localStorage.removeItem('avue-form')
-        }
-      },
-      deep: true
     },
-    beautifierOptions: {
-      handler (val) {
-        if (this.storage) {
-          localStorage.setItem('avue-form-beautifier-options', JSON.stringify(val))
-        }
-      },
-      deep: true
+    showGithubStar: {
+      type: Boolean,
+      default: true
+    },
+    toolbar: {
+      type: Array,
+      default: () => {
+        return ['import', 'generate', 'preview', 'clear']
+      }
+    },
+    undoRedo: {
+      type: Boolean,
+      default: true
+    },
+    includeFields: {
+      type: Array,
+      default: () => {
+        const arr = []
+        fields.forEach(f => {
+          f.list.forEach(c => {
+            arr.push(c.type)
+          })
+        })
+        return arr
+      }
+    },
+    customFields: {
+      type: Array,
     },
+    defaultValues: {
+      type: Object
+    },
+  },
+  watch: {
     options: {
-      handler (val) {
-        this.transAvueOptionsToFormDesigner(val).then(res => {
+      handler(val) {
+        let options = val
+        if (typeof options == 'string') {
+          try {
+            options = eval('(' + options + ')')
+          } catch (e) {
+            console.error('非法配置')
+            options = { column: [] }
+          }
+        }
+        this.transAvueOptionsToFormDesigner(options).then(res => {
           this.widgetForm = { ...this.widgetForm, ...res }
         })
       },
@@ -254,14 +378,14 @@ export default {
     }
   },
   computed: {
-    leftWidth () {
+    leftWidth() {
       if (typeof this.asideLeftWidth == 'string') {
         return this.asideLeftWidth
       } else {
         return `${this.asideLeftWidth}px`
       }
     },
-    rightWidth () {
+    rightWidth() {
       if (typeof this.asideRightWidth == 'string') {
         return this.asideRightWidth
       } else {
@@ -269,7 +393,7 @@ export default {
       }
     }
   },
-  data () {
+  data() {
     return {
       widgetEmpty,
       fields,
@@ -281,150 +405,214 @@ export default {
         gutter: 0,
         menuBtn: true,
         submitBtn: true,
-        submitSize: 'medium',
         submitText: '提交',
         emptyBtn: true,
-        emptySize: 'medium',
         emptyText: '清空',
         menuPosition: 'center'
       },
-      widgetFormPreview: {},
+      option: {},
       configTab: 'widget',
       widgetFormSelect: {},
       previewVisible: false,
       generateJsonVisible: false,
       importJsonVisible: false,
       importJson: {},
-      widgetModels: {},
-      configOption: {},
-      beautifierOptions: {
-        enabled: false,
+      form: {},
+      configOption: {
+        generateType: 'json',
         space: 2,
         quoteType: 'single',
-        dropQuotesOnKeys: true,
-        dropQuotesOnNumbers: false
+        dropQuotesOnKeys: true
+      },
+      history: {
+        index: 0, // 当前下标
+        maxStep: 20, // 最大记录步数
+        steps: [], // 历史步数
       }
     }
   },
-  mounted () {
-    this.handleLoadCss();
-    this.handleLoadStorage();
-    this.loadBeautifierOptions();
+  mounted() {
+    this.handleLoadStorage()
+    this.handleLoadCss()
   },
   methods: {
     // 组件初始化时加载本地存储中的options(需开启storage),若不存在则读取用户配置的options
-    handleLoadStorage () {
-      if (this.storage) {
-        const form = localStorage.getItem('avue-form')
-        if (form) this.transAvueOptionsToFormDesigner(JSON.parse(form)).then(data => this.widgetForm = data)
-      } else this.transAvueOptionsToFormDesigner({ ...this.widgetForm, ...this.options }).then(data => this.widgetForm = data)
-    },
-    // 获取JSON格式化属性
-    loadBeautifierOptions () {
-      const bo = localStorage.getItem('avue-form-beautifier-options')
-      if (bo) this.beautifierOptions = JSON.parse(bo)
+    async handleLoadStorage() {
+      let options = this.options
+      if (typeof options == 'string') {
+        try {
+          options = eval('(' + options + ')')
+        } catch (e) {
+          console.error('非法配置')
+          options = { column: [] }
+        }
+      }
+      if (!options.column) options.column = []
+      this.widgetForm = this.initHistory({
+        index: 0,
+        maxStep: 20,
+        steps: [await this.transAvueOptionsToFormDesigner({ ...this.widgetForm, ...options })],
+        storage: this.storage
+      })
+
+      if (this.undoRedo) {
+        window.addEventListener('keydown', (evt) => {
+          // 监听 cmd + z / ctrl + z 撤销
+          if ((evt.metaKey && !evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && !evt.shiftKey && evt.keyCode == 90)) {
+            this.widgetForm = this.handleUndo()
+          }
+
+          // 监听 cmd + shift + z / ctrl + shift + z / ctrl + y 重做
+          if ((evt.metaKey && evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && evt.shiftKey && evt.keyCode == 90) || (evt.ctrlKey && evt.keyCode == 89)) {
+            this.widgetForm = this.handleRedo()
+          }
+        }, false)
+      }
+
     },
-    // 加载阿里iconfront
-    handleLoadCss () {
-      const url = '//at.alicdn.com/t/font_1254447_rwaizg76pz.css'
-      const link = document.createElement('link');
-      link.rel = 'stylesheet';
-      link.href = url;
-      window.document.head.appendChild(link)
+    // 加载icon
+    handleLoadCss() {
+      const head = document.getElementsByTagName('head')[0]
+      const script = document.createElement('link')
+      script.rel = 'stylesheet'
+      script.type = 'text/css'
+      script.href = 'https://at.alicdn.com/t/font_1254447_zc9iezc230c.css'
+      head.appendChild(script)
+      // this.loadScript('css', 'https://at.alicdn.com/t/font_1254447_zc9iezc230c.css')
     },
     // Avue文档链接
-    handleAvueDoc () {
+    handleAvueDoc() {
       window.open('https://avuejs.com/doc/form/form-doc', '_blank')
     },
+    // 左侧字段点击
+    handleFieldClick(item) {
+      const activeIndex = this.widgetForm.column.findIndex(c => c.prop == this.widgetFormSelect.prop) + 1
+      let newIndex = 0
+      if (activeIndex == -1) {
+        this.widgetForm.column.push(item)
+        newIndex = this.widgetForm.column.length - 1
+      } else {
+        this.widgetForm.column.splice(activeIndex, 0, item)
+        newIndex = activeIndex
+      }
+
+      this.$refs.widgetForm.handleWidgetAdd({ newIndex })
+    },
     // 预览 - 弹窗
-    handlePreview () {
+    handlePreview() {
       if (!this.widgetForm.column || this.widgetForm.column.length == 0) this.$message.error("没有需要展示的内容")
       else {
-        this.transformToAvueOptions(this.widgetForm).then(data => {
-          this.widgetFormPreview = data
+        this.transformToAvueOptions(this.widgetForm, true).then(data => {
+          this.option = data
           this.previewVisible = true
         })
       }
     },
     // 导入JSON - 弹窗 - 确定
-    handleImportJsonSubmit () {
+    handleImportJsonSubmit() {
       try {
         this.transAvueOptionsToFormDesigner(this.importJson).then(res => {
           this.widgetForm = res
           this.importJsonVisible = false
+          this.handleHistoryChange(this.widgetForm)
         })
       } catch (e) {
         this.$message.error(e.message)
       }
     },
     // 生成JSON - 弹窗
-    handleGenerateJson () {
+    handleGenerateJson() {
       this.transformToAvueOptions(this.widgetForm).then(data => {
-        this.widgetFormPreview = data
+        this.option = data
         this.generateJsonVisible = true
       })
     },
     // 生成JSON - 弹窗 - 确定
-    handleGenerate () {
+    handleGenerate() {
       this.transformToAvueOptions(this.widgetForm).then(data => {
-        this.$emit('submit', data)
+        if (this.configOption.generateType && this.configOption.generateType == 'string') this.$emit('submit', beautifier(data, {
+          minify: true,
+          ...this.configOption
+        }))
+        else this.$emit('submit', data)
       })
     },
     // 生成JSON - 弹窗 - 拷贝
-    handleCopy () {
-      this.$Clipboard({
-        text: this.getCopyContent()
-      }).then(() => {
-        this.$message.success('复制成功')
-      }).catch(() => {
-        this.$message.error('复制失败')
-      });
+    handleCopy() {
+      this.transformToAvueOptions(this.widgetForm).then(data => {
+        this.$Clipboard({
+          text: beautifier(data, {
+            minify: true,
+            ...this.configOption
+          })
+        }).then(() => {
+          this.$message.success('复制成功')
+        }).catch(() => {
+          this.$message.error('复制失败')
+        })
+      })
     },
     // 预览 - 弹窗 - 确定
-    handlePreviewSubmit () {
-      this.$refs.form.validate((valid) => {
-        if (valid) this.$alert(this.widgetModels).catch(() => {
+    handlePreviewSubmit(form, done) {
+      if (done) {
+        this.$alert(this.form).then(() => {
+          done()
+        }).catch(() => {
+          done()
         })
-      })
+      } else {
+        this.$refs.form.validate((valid, done) => {
+          if (valid) this.$alert(this.form).then(() => {
+            done()
+          }).catch(() => {
+            done()
+          })
+        })
+      }
     },
     // 预览 - 弹窗 - 关闭前
-    handleBeforeClose () {
-      this.$refs.form.resetForm();
+    handleBeforeClose() {
+      this.$refs.form.resetForm()
+      this.form = {}
       this.previewVisible = false
     },
     // 清空
-    handleClear () {
+    handleClear() {
       if (this.widgetForm && this.widgetForm.column && this.widgetForm.column.length > 0) {
         this.$confirm('确定要清空吗?', '警告', {
           type: 'warning'
         }).then(() => {
-          this.widgetForm = { column: [] }
-          this.widgetFormSelect = {}
+          this.$set(this.widgetForm, 'column', [])
+          this.$set(this, 'form', {})
+          this.$set(this, 'widgetFormSelect', {})
+          this.handleHistoryChange(this.widgetForm)
         }).catch(() => {
         })
       } else this.$message.error("没有需要清空的内容")
     },
-    /**
-     * 获取需要复制的内容
-     * @return {String}
-     */
-    getCopyContent () {
-      if (this.beautifierOptions.enabled) return beautifier(this.widgetFormPreview, this.beautifierOptions)
-      else return JSON.stringify(this.widgetFormPreview, null, 2)
-    },
     // 表单设计器配置项 转化为 Avue配置项
-    transformToAvueOptions (obj) {
+    transformToAvueOptions(obj, isPreview = false) {
+      const _this = this
       return new Promise((resolve, reject) => {
         try {
-          const data = this.deepClone(obj)
+          const data = _this.deepClone(obj)
           for (let i = 0; i < data.column.length; i++) {
             const col = data.column[i]
+
+            if (isPreview) { // 预览调整事件中的this指向
+              let event = ['change', 'blur', 'click', 'focus']
+              event.forEach(e => {
+                if (col[e]) col[e] = eval((col[e] + '').replace(/this/g, '_this'))
+              })
+              if (col.event) Object.keys(col.event).forEach(key => col.event[key] = eval((col.event[key] + '').replace(/this/g, '_this')))
+            }
+
             if (col.type == 'dynamic' && col.children && col.children.column && col.children.column.length > 0) {
               const c = col.children.column;
               c.forEach(item => {
                 delete item.subfield
               })
-              this.transformToAvueOptions(col.children).then(res => {
+              this.transformToAvueOptions(col.children, isPreview).then(res => {
                 col.children = res
               })
             } else if (col.type == 'group') {
@@ -434,8 +622,11 @@ export default {
                 label: col.label,
                 icon: col.icon,
                 prop: col.prop,
+                arrow: col.arrow,
+                collapse: col.collapse,
+                display: col.display
               }
-              this.transformToAvueOptions(col.children).then(res => {
+              this.transformToAvueOptions(col.children, isPreview).then(res => {
                 group.column = res.column
                 data.group.push(group)
               })
@@ -446,33 +637,37 @@ export default {
                 delete col.dicUrl
                 delete col.dicMethod
                 delete col.dicQuery
+                delete col.dicQueryConfig
               } else if (col.dicOption == 'remote') {
                 delete col.dicData
-                if (col.dicQuery && col.dicQuery.length > 0) {
+                if (col.dicQueryConfig && col.dicQueryConfig.length > 0) {
                   const query = {}
-                  col.dicQuery.forEach(q => {
+                  col.dicQueryConfig.forEach(q => {
                     if (q.key && q.value) query[q.key] = q.value
                   })
                   col.dicQuery = query
-                } else delete col.dicQuery
+                  delete col.dicQueryConfig
+                } else delete col.dicQueryConfig
               }
               delete col.dicOption
             } else if (['upload'].includes(col.type)) {
-              if (col.headers && col.headers.length > 0) {
+              if (col.headersConfig && col.headersConfig.length > 0) {
                 const headers = {}
-                col.headers.forEach(h => {
+                col.headersConfig.forEach(h => {
                   if (h.key && h.value) headers[h.key] = h.value
                 })
                 col.headers = headers
-              }
+              } else delete col.headers
+              delete col.headersConfig
 
-              if (col.data && col.data.length > 0) {
+              if (col.dataConfig && col.dataConfig.length > 0) {
                 const data = {}
-                col.data.forEach(h => {
+                col.dataConfig.forEach(h => {
                   if (h.key && h.value) data[h.key] = h.value
                 })
                 col.data = data
-              }
+              } else delete col.data
+              delete col.dataConfig
             }
           }
           resolve(data)
@@ -482,7 +677,8 @@ export default {
       })
     },
     // Avue配置项 转化为 表单设计器配置项
-    transAvueOptionsToFormDesigner (obj) {
+    transAvueOptionsToFormDesigner(obj) {
+      if (typeof obj == 'string') obj = eval('(' + obj + ')')
       const data = this.deepClone(obj)
       return new Promise((resolve, reject) => {
         try {
@@ -506,7 +702,7 @@ export default {
                       $cellEdit: true
                     })
                   }
-                  col.dicQuery = arr
+                  col.dicQueryConfig = arr
                 }
                 if (col.dicUrl) col.dicOption = 'remote'
                 else col.dicOption = 'static'
@@ -521,8 +717,8 @@ export default {
                       $cellEdit: true
                     })
                   }
-                  col.headers = arr
-                }
+                  col.headersConfig = arr
+                } else col.headersConfig = []
 
                 if (col.data && typeof col.data == 'object') {
                   const arr = []
@@ -533,8 +729,8 @@ export default {
                       $cellEdit: true
                     })
                   }
-                  col.data = arr
-                }
+                  col.dataConfig = arr
+                } else col.dataConfig = []
               }
             })
           }
@@ -548,6 +744,9 @@ export default {
                 label: col.label,
                 icon: col.icon,
                 prop: col.prop,
+                arrow: col.arrow,
+                collapse: col.collapse,
+                display: col.display
               }
               this.transAvueOptionsToFormDesigner(col).then(res => {
                 group.children = res
@@ -561,49 +760,18 @@ export default {
           reject(e)
         }
       })
+    },
+    async getData(type = 'json', option = {}) {
+      if (type == 'string') return beautifier(await this.transformToAvueOptions(this.widgetForm), {
+        minify: true,
+        ...option
+      })
+      else return await this.transformToAvueOptions(this.widgetForm)
     }
   }
 }
 </script>
 
 <style lang="scss">
-@import "./styles/index.scss";
-
-.drawer-foot {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  padding: 20px;
-  display: flex;
-
-  button {
-    width: 50%;
-  }
-}
-
-.drawer-foot > span {
-  display: inline-block;
-  width: 50%;
-  button {
-    width: 100%;
-  }
-}
-
-.popper-bo {
-  .el-alert {
-    margin-bottom: 10px;
-  }
-}
-
-.preview-form {
-  overflow-y: scroll;
-  height: 83vh;
-}
-
-.widget-config-container {
-  .avue-group__item {
-    padding: 0;
-  }
-}
+@import './styles/index.scss';
 </style>

+ 99 - 23
packages/styles/index.scss

@@ -15,7 +15,7 @@ body {
 }
 
 #app {
-  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  font-family: "Avenir", Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   color: #2c3e50;
@@ -25,6 +25,25 @@ body {
 
 .form-designer {
   height: 100%;
+  background: #fff;
+  outline: 1px solid #e4e7ed;
+
+  ::-webkit-scrollbar {
+    width: 0px !important;
+    height: 0px !important;
+  }
+
+  ::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0) !important;
+    border-radius: 10px !important;
+    background-color: rgba(0, 0, 0, 0) !important;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    border-radius: 10px !important;
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0) !important;
+    background-color: #fff !important;
+  }
 
   .fields-list {
     padding: 10px 0;
@@ -68,6 +87,7 @@ body {
         cursor: move;
         background: #f4f6fc;
         border: 1px solid #f4f6fc;
+        color: #333;
 
         .icon {
           margin-right: 6px;
@@ -127,15 +147,16 @@ body {
   }
 
   .widget-container {
+    flex: 1;
     border-left: 1px solid #e0e0e0;
     border-right: 1px solid #e0e0e0;
 
     .widget-container-header {
       height: 45px !important;
-      line-height: 45px;
-      font-size: 18px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
       border-bottom: solid 2px #e4e7ed;
-      text-align: right;
     }
 
     .el-main {
@@ -145,18 +166,29 @@ body {
   }
 
   .widget-form-container {
-    position: absolute;
+    height: 100%;
+    position: relative;
     top: 0;
     left: 0;
     right: 0;
     bottom: 0;
 
+    .el-form {
+      height: 100%;
+      .el-row {
+        height: 100%;
+      }
+    }
+
     .widget-form-list {
       height: 100%;
-      min-height: calc(100vh - 45px);
-      padding-bottom: 20px;
+      min-height: calc(100% - 45px);
+      padding-bottom: 50px;
+      overflow: hidden;
+      overflow-y: scroll;
 
       .widget-form-item {
+        min-height: 55px;
         padding: 5px 10px 18px;
         margin: 0;
         position: relative;
@@ -164,7 +196,7 @@ body {
 
         &.required {
           .el-form-item__label::before {
-            content: '*';
+            content: "*";
             color: #f56c6c;
             margin-right: 4px;
           }
@@ -226,7 +258,7 @@ body {
             border-left: 5px solid $primary-color;
             box-sizing: border-box;
             font-size: 0;
-            content: '';
+            content: "";
             overflow: hidden;
             padding: 0 !important;
             position: relative;
@@ -248,7 +280,7 @@ body {
 
           &.required {
             .el-table__header .cell::before {
-              content: '*';
+              content: "*";
               color: #f56c6c;
               margin-right: 4px;
             }
@@ -272,7 +304,7 @@ body {
             bottom: 0;
             top: 0;
             display: block;
-            content: '';
+            content: "";
             z-index: 1001;
           }
 
@@ -354,7 +386,7 @@ body {
             border-left: 5px solid $primary-color;
             box-sizing: border-box;
             font-size: 0;
-            content: '';
+            content: "";
             overflow: hidden;
             padding: 0 !important;
             position: relative;
@@ -376,7 +408,7 @@ body {
 
           &.required {
             .el-form-item__label::before {
-              content: '*';
+              content: "*";
               color: #f56c6c;
               margin-right: 4px;
             }
@@ -400,7 +432,7 @@ body {
             bottom: 0;
             top: 0;
             display: block;
-            content: '';
+            content: "";
             z-index: 1001;
           }
 
@@ -465,7 +497,7 @@ body {
       border-top: 5px solid $primary-color;
       box-sizing: border-box;
       font-size: 0;
-      content: '';
+      content: "";
       overflow: hidden;
       padding: 0 !important;
       position: relative;
@@ -477,20 +509,23 @@ body {
   }
 
   .widget-config-container {
-    margin-top: 5px;
-    //position: relative;
+    height: 100%;
+    padding-top: 5px;
+
+    .el-tabs {
+      height: 100%;
+    }
 
     .el-tabs__header {
-      position: fixed;
-      top: 5px;
-      width: 380px;
+      margin: 0;
       height: 45px;
       background: white;
       z-index: 1000;
     }
 
     .el-tabs__content {
-      margin-top: 50px;
+      height: calc(100% - 45px);
+      overflow-y: scroll;
     }
 
     .widget-config {
@@ -533,11 +568,15 @@ body {
   }
 
   .el-container {
-    height: 100vh;
+    height: 100%;
 
     .el-main {
       height: calc(100% - 45px);
     }
+
+    .el-aside {
+      background: #fff;
+    }
   }
 
   .danger {
@@ -547,6 +586,23 @@ body {
   .warning {
     color: #e6a23c;
   }
+
+  .el-input-number.is-controls-right {
+    .el-input-number__decrease {
+      border-left: none;
+    }
+    .el-input-number__increase {
+      border-left: none;
+      border-bottom: none;
+    }
+  }
+
+  .el-date-editor,
+  .el-cascader,
+  .el-select,
+  .el-input-number {
+    width: 100%;
+  }
 }
 
 .drawer-foot {
@@ -560,9 +616,29 @@ body {
   button {
     width: 50%;
   }
+
+  & > span {
+    display: inline-block;
+    width: 50%;
+    button {
+      width: 100%;
+    }
+  }
 }
 
 .preview-form {
   overflow-y: scroll;
   height: 83vh;
-}
+}
+
+.popper-bo {
+  .el-alert {
+    margin-bottom: 10px;
+  }
+}
+
+.el-drawer__wrapper {
+  :focus {
+    outline: 0;
+  }
+}

+ 0 - 19
src/components/common/cy-form-design.vue

@@ -20,25 +20,6 @@
       data(){
         return {
           options: {
-            detail: true,
-            column: [
-              {
-                type: 'url',
-                prop: 'url',
-                label: '超链接',
-                icon: 'icon-url',
-                valueDefault: 'http://www.baidu.com',
-                span: 24,
-                display: true
-              },
-              {
-                prop: 'img',
-                type: 'img',
-                label: '图片',
-                span: '24',
-                display: true
-              }
-            ]
           },
         }
       },

+ 2 - 1
src/main.js

@@ -22,8 +22,9 @@ import thirdRegister from './components/third-register/main';
 import avueUeditor from 'avue-plugin-ueditor';
 import website from '@/config/website';
 import crudCommon from '@/mixins/crud';
-import AvueFormDesign from 'avue-form-design';
+// import AvueFormDesign from 'avue-form-design';
 // import AvueFormDesign from '../packages/';
+import AvueFormDesign from '@sscfaith/avue-form-design'
 Vue.use(AvueFormDesign)
 // 注册全局crud驱动
 window.$crudCommon = crudCommon;