Browse Source

开发ing

zhoujump 2 months ago
parent
commit
1cedaa654b

+ 3 - 0
src/App.vue

@@ -118,4 +118,7 @@ export default {
 *{
   box-sizing: border-box;
 }
+ input[aria-hidden="true"]{
+   display: none !important;
+ }
 </style>

+ 31 - 0
src/api/form.js

@@ -163,4 +163,35 @@ export function getAudienceList(page, page_size,keyword,is_export,expo_id){
     }
   })
 }
+/**
+ * 保存自定义字段
+ * @param id
+ * @param field_label
+ * @param field_type
+ * @param field_data
+ * @param is_required
+ * @returns {*}
+ */
+export function saveMyFields(id,field_label,field_type,field_data,is_required){
+  return request({
+    url: '/api/form/my-fields',
+    method: 'post',
+    data: {
+      id,
+      field_label,
+      field_type,
+      field_data,
+      is_required
+    }
+  })
+}
 
+/**
+ * 获取自定义字段
+ */
+export function getMyFields(){
+  return request({
+    url: '/api/form/my-fields',
+    method: 'get'
+  })
+}

+ 7 - 1
src/router/index.js

@@ -66,7 +66,7 @@ export const constantRoutes = [
           },
           {
             path: 'edit/:id',
-            component: () => import('@/views/preRegManage/edit'),
+            component: () => import('@/views/preRegManage/edit.vue'),
             name: 'preRegManagEdit',
             meta: { title: '表单编辑', icon: 'el-icon-edit', hidden: true, roles: 'preReg.edit', collapse: true }
           },
@@ -75,6 +75,12 @@ export const constantRoutes = [
             component: () => import('@/views/preRegManage/edit'),
             name: 'preRegManagAdd',
             meta: { title: '表单新增', icon: 'el-icon-document-add', roles: 'preReg.edit', collapse: true }
+          },
+          {
+            path: 'comp',
+            component: () => import('@/views/preRegManage/compEdit.vue'),
+            name: 'preRegManagcomp',
+            meta: { title: '我的组件', icon: 'el-icon-menu', roles: 'preReg.comp' }
           }
         ]
       },

+ 43 - 37
src/views/audienceManage/index.vue

@@ -48,6 +48,46 @@ export default Vue.extend({
     this.getInvitationList()
   },
   methods: {
+    getLabelName(key){
+      let name = {
+        address: '地址',
+        city: '城市',
+        company: '公司名称',
+        country: '国家',
+        create_time: '创建时间',
+        department: '部门',
+        email: '邮箱',
+        end_date: '结束时间',
+        expo_name: '展会名称',
+        expo_id: '展会ID',
+        full_name: '姓名',
+        first_name: '姓',
+        last_name: '名',
+        id: 'ID',
+        id_number: '证件号码',
+        id_type: '证件类型',
+        industry: '行业',
+        ip: 'IP',
+        location: '位置',
+        mobile: '手机号',
+        mobile_country_code: '区号',
+        organizer: '展商名称',
+        position: '职位',
+        province: '省/州',
+        send_invitation_id: '发送邀请函ID',
+        start_date: '开始时间',
+        status: '状态',
+        template_id: '模板ID',
+        template_name: '模板名称',
+        update_time: '更新时间',
+        user_id: '用户ID',
+      }
+      if(name[key]){
+        return name[key]
+      }else {
+        return key
+      }
+    },
     getInvitationList() {
       getTemplateList(++this.invitation_data.page, 10).then(res => {
         console.log(res)
@@ -166,44 +206,10 @@ export default Vue.extend({
         <el-table-column type="expand">
           <template slot-scope="props">
             <el-form class="expand-form" :inline="true" label-position="left">
-              <el-form-item label="姓名">
-                {{ props.row.full_name }}
-              </el-form-item>
-              <el-form-item label="参展名称">
-                <expo-popover placement="bottom" trigger="hover" :expo-id="''+props.row.expo_id">
-                  {{ props.row.expo_name }}
-                </expo-popover>
-              </el-form-item>
-              <el-form-item label="邮箱">
-                {{ props.row.email }}
-              </el-form-item>
-              <el-form-item label="区号">
-                {{ props.row.mobile_country_code }}
-              </el-form-item>
-              <el-form-item label="电话">
-                {{ props.row.mobile }}
-              </el-form-item>
-              <el-form-item label="国家">
-                {{ props.row.country }}
-              </el-form-item>
-              <el-form-item label="省份">
-                {{ props.row.province }}
-              </el-form-item>
-              <el-form-item label="城市">
-                {{ props.row.city }}
-              </el-form-item>
-              <el-form-item label="地址">
-                {{ props.row.address }}
-              </el-form-item>
-              <el-form-item label="公司">
-                {{ props.row.company }}
-              </el-form-item>
-              <el-form-item label="部门">
-                {{ props.row.department }}
-              </el-form-item>
-              <el-form-item label="职位">
-                {{ props.row.position }}
+              <el-form-item v-for="(item,key) in props.row" :label="getLabelName(key)+':'" :key="key">
+                {{ item }}
               </el-form-item>
+
             </el-form>
           </template>
         </el-table-column>

+ 5 - 5
src/views/exhibitorManage/exhibitorSetting.vue

@@ -53,12 +53,12 @@ export default Vue.extend({
           this.loading = false
           console.log(err)
         })
-        getFormList(1, 1000).then(res => {
-          this.formList = res.data.data
-        }).catch(err => {
-          console.log(err)
-        })
       }
+      getFormList(1, 1000).then(res => {
+        this.formList = res.data.data
+      }).catch(err => {
+        console.log(err)
+      })
     },
     uploadImage(event, type) {
       this.loading = true

+ 852 - 0
src/views/preRegManage/compEdit.vue

@@ -0,0 +1,852 @@
+<script lang="ts">
+import Vue from 'vue'
+import { getMyFields, saveMyFields } from '@/api/form'
+import guide from '@/views/components/guidePopover.vue'
+import draggable from 'vuedraggable'
+import countryCode from '@/lib/countryCode.json'
+import {
+  pcTextArr,
+  pcaTextArr
+} from 'element-china-area-data'
+
+export default Vue.extend({
+  name: 'Edit',
+  components: {
+    draggable,
+    guide
+  },
+  data() {
+    return {
+      compList: [
+
+      ],
+      currentData: {
+        id: '',
+        field_name: '',
+        field_type: 'input',
+        field_label: '请在下方输入名字',
+        field_data: {
+          value: '',
+          width: 6,
+          placeholder: '请输入名字',
+          pattern: '',
+          options: [],
+          max: 10,
+          min: 0,
+          step: 1,
+          rows: [2, 4],
+          rangeSeparator: '至',
+          endPlaceholder: '结束时间'
+        },
+        is_required: 0
+      },
+      selectInput: '',
+      pcTextArr,
+      pcaTextArr,
+      countryCode,
+      copyShow: false,
+      loading: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init() {
+      this.getFields()
+    },
+    getFields() {
+      if (this.loading) { return }
+      this.loading = true
+      getMyFields().then(res => {
+        if (this.compList.length === 0 && res.data.length > 0) {
+          this.currentData = res.data[0]
+        }
+        this.compList = res.data
+        this.loading = false
+      }).catch(err => {
+        this.loading = false
+        this.$notify({
+          title: '出错了',
+          message: err.message,
+          type: 'error'
+        })
+      })
+    },
+    save() {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      saveMyFields(this.currentData.id, this.currentData.field_label, this.currentData.field_type, this.currentData.field_data, this.currentData.is_required).then(res => {
+        this.loading = false
+        this.$notify({
+          title: '保存成功',
+          message: '组件已保存完成',
+          type: 'success'
+        })
+        this.getFields()
+      }).catch(err => {
+        this.loading = false
+        this.$notify({
+          title: '出错了',
+          message: err.message,
+          type: 'error'
+        })
+      })
+    },
+    choseComp(element) {
+      this.currentData = element
+    },
+    addSelectItem() {
+      if (this.selectInput.length === 0) { return }
+      this.currentData.field_data.options.push({
+        label: this.selectInput,
+        value: this.selectInput
+      })
+      this.selectInput = ''
+    },
+    removeSelectItem(index) {
+      this.currentData.field_data.options.splice(index, 1)
+    },
+    cloneItem(currentData) {
+      if(this.loading) { return }
+      this.loading = true
+      this.copyShow=false
+      saveMyFields('', currentData.field_label + '(副本)', currentData.field_type, currentData.field_data, currentData.is_required).then(res => {
+        this.$notify({
+          title: '复制成功',
+          message: '组件副本已保存完成',
+          type: 'success'
+        })
+        this.loading = false
+        this.save()
+      }).catch(err => {
+        this.loading = false
+        this.$notify({
+          title: '出错了',
+          message: err.message,
+          type: 'error'
+        })
+      })
+    },
+    creatNew() {
+      this.save()
+      this.currentData = {
+        id: '',
+        field_name: '',
+        field_type: 'input',
+        field_label: '请在下方输入名字',
+        field_data: {
+          value: '',
+          width: 6,
+          placeholder: '请输入名字',
+          pattern: '',
+          options: [],
+          max: 10,
+          min: 0,
+          step: 1,
+          rows: [2, 4],
+          rangeSeparator: '至'
+        },
+        is_required: 0
+      }
+    }
+  }
+})
+</script>
+
+<template>
+  <div v-loading="loading" class="main-box">
+    <div class="comp-lib">
+      <div class="add el-icon-plus" @click="creatNew" />
+      <div class="title">
+        自定义组件库
+      </div>
+      <div class="list">
+        <div class="drag-list">
+          <div class="drag-cont">
+            <div v-for="(element) in compList" :key="element.id" @click="choseComp(element)">
+              <div v-if="element.field_type==='tips'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+              </div>
+              <div v-if="element.field_type==='input'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-input v-model="element.field_data.value" :placeholder="element.field_data.placeholder" />
+              </div>
+              <div v-if="element.field_type==='email'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <div>
+                  <el-input v-model="element.field_data.value" :placeholder="element.field_data.placeholder" />
+                </div>
+                <div class="code-input">
+                  <el-input v-model="element.field_data.value" :placeholder="element.field_data.codePlaceholder" />
+                  <el-button>获取验证码</el-button>
+                </div>
+              </div>
+              <div v-if="element.field_type==='phone'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <div class="phone-input">
+                  <el-select v-model="element.field_data.value" :placeholder="element.field_data.placeholder">
+                    <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :value="item.phone_code" :label="'+'+item.phone_code+'('+item.chinese_name+')'" />
+                  </el-select>
+                </div>
+              </div>
+              <div v-if="element.field_type==='select'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-select v-model="element.field_data.value" :placeholder="element.field_data.placeholder">
+                  <el-option v-for="item in element.field_data.options" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </div>
+              <div v-if="element.field_type==='checkbox'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-checkbox-group v-model="element.field_data.value">
+                  <el-checkbox v-for="item in element.field_data.options" :key="item.value" :label="item.label" />
+                </el-checkbox-group>
+              </div>
+              <div v-if="element.field_type==='radio'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-radio-group v-model="element.field_data.value">
+                  <el-radio v-for="item in element.field_data.options" :key="item.value" :label="item.label" />
+                </el-radio-group>
+              </div>
+              <div v-if="element.field_type==='number'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-input-number v-model="element.field_data.value" :max="element.field_data.max" :min="element.field_data.min" :step="element.field_data.step" prefix-icon="el-icon-user" :placeholder="element.field_data.placeholder" />
+              </div>
+              <div v-if="element.field_type==='slider'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-slider v-model="element.field_data.value" :max="element.field_data.max" :min="element.field_data.min" :step="element.field_data.step" prefix-icon="el-icon-user" :placeholder="element.field_data.placeholder" />
+              </div>
+              <div v-if="element.field_type==='textarea'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-input v-model="element.field_data.value" type="textarea" :placeholder="element.field_data.placeholder" :autosize="{ minRows: element.field_data.rows[0], maxRows: element.field_data.rows[1]}" />
+              </div>
+              <div v-if="element.field_type==='region'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-cascader
+                  :placeholder="element.field_data.placeholder"
+                  size="large"
+                  :options="element.field_data.range===2?pcTextArr:pcaTextArr"
+                  value="element.field_data.value"
+                />
+              </div>
+              <div v-if="element.field_type==='time'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-time-picker v-model="element.field_data.value" :placeholder="element.field_data.placeholder" />
+              </div>
+              <div v-if="element.field_type==='timeRange'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item','view']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-time-picker v-model="element.field_data.value" is-range :start-placeholder="element.field_data.placeholder" :end-placeholder="element.field_data.endPlaceholder" :range-separator="element.field_data.rangeSeparator" />
+              </div>
+              <div v-if="element.field_type==='date'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-date-picker v-model="element.field_data.value" :placeholder="element.field_data.placeholder" />
+              </div>
+              <div v-if="element.field_type==='dateRange'" :class="[element.field_type,element.id===currentData.id?'active':'','form-item']">
+                <div class="tips">{{ element.field_label }}</div>
+                <el-date-picker v-model="element.field_data.value" type="daterange" :start-placeholder="element.field_data.placeholder" :end-placeholder="element.field_data.endPlaceholder" :range-separator="element.field_data.rangeSeparator" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="form-view">
+      <div class="scroll-view">
+        <div class="form-cont">
+          <div class="form-head">
+            <div class="title">示例表单</div>
+            <div class="tips">在右侧菜单自定义当前组件</div>
+          </div>
+          <div class="form-body">
+            <div class="drag-cont">
+              <div :style="{gridColumn:'span '+currentData.field_data.width}" :class="[currentData.is_required?'required':'']">
+                <div v-if="currentData.field_type==='tips'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                </div>
+                <div v-if="currentData.field_type==='input'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-input v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder" />
+                </div>
+                <div v-if="currentData.field_type==='email'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <div>
+                    <el-input v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder" />
+                  </div>
+                  <div class="code-input">
+                    <el-input v-model="currentData.field_data.value" :placeholder="currentData.field_data.codePlaceholder" />
+                    <el-button>获取验证码</el-button>
+                  </div>
+                </div>
+                <div v-if="currentData.field_type==='phone'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <div class="phone-input">
+                    <el-select v-model="currentData.field_data.country" :placeholder="currentData.field_data.placeholder">
+                      <el-option v-for="(item,index) in currentData.field_data.countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.country_code" />
+                    </el-select>
+                    <el-input v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder" />
+                  </div>
+                </div>
+                <div v-if="currentData.field_type==='select'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-select v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder">
+                    <el-option v-for="item in currentData.field_data.options" :key="item.value" :label="item.label" :value="item.value" />
+                  </el-select>
+                </div>
+                <div v-if="currentData.field_type==='checkbox'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-checkbox-group v-model="currentData.field_data.value">
+                    <el-checkbox v-for="item in currentData.field_data.options" :key="item.value" :label="item.value" />
+                  </el-checkbox-group>
+                </div>
+                <div v-if="currentData.field_type==='radio'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-radio-group v-model="currentData.field_data.value">
+                    <el-radio v-for="item in currentData.field_data.options" :key="item.value" :label="item.label" />
+                  </el-radio-group>
+                </div>
+                <div v-if="currentData.field_type==='number'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-input-number v-model="currentData.field_data.value" :max="currentData.field_data.max" :min="currentData.field_data.min" :step="currentData.field_data.step" prefix-icon="el-icon-user" :placeholder="currentData.field_data.placeholder" />
+                </div>
+                <div v-if="currentData.field_type==='slider'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-slider v-model="currentData.field_data.value" :max="currentData.field_data.max" :min="currentData.field_data.min" :step="currentData.field_data.step" prefix-icon="el-icon-user" :placeholder="currentData.field_data.placeholder" />
+                </div>
+                <div v-if="currentData.field_type==='textarea'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-input v-model="currentData.field_data.value" type="textarea" :placeholder="currentData.field_data.placeholder" :autosize="{ minRows: currentData.field_data.rows[0], maxRows: currentData.field_data.rows[1]}" />
+                </div>
+                <div v-if="currentData.field_type==='region'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-cascader
+                    v-model="currentData.field_data.value"
+                    :placeholder="currentData.field_data.placeholder"
+                    size="large"
+                    :options="currentData.field_data.range===2?pcTextArr:pcaTextArr"
+                  />
+                </div>
+                <div v-if="currentData.field_type==='time'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-time-picker :key="currentData.field_type" v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder" />
+                </div>
+                <div v-if="currentData.field_type==='timeRange'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-time-picker :key="currentData.field_type" v-model="currentData.field_data.value" is-range :start-placeholder="currentData.field_data.placeholder" :end-placeholder="currentData.field_data.endPlaceholder" :range-separator="currentData.field_data.rangeSeparator" />
+                </div>
+                <div v-if="currentData.field_type==='date'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-date-picker :key="currentData.field_type" v-model="currentData.field_data.value" :placeholder="currentData.field_data.placeholder" />
+                </div>
+                <div v-if="currentData.field_type==='dateRange'" :class="[currentData.field_type,'form-item','view']">
+                  <div class="tips">{{ currentData.field_label }}</div>
+                  <el-date-picker :key="currentData.field_type" v-model="currentData.field_data.value" type="daterange" :start-placeholder="currentData.field_data.placeholder" :end-placeholder="currentData.field_data.endPlaceholder" :range-separator="currentData.field_data.rangeSeparator" />
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="comp-edit">
+      <div class="button">
+        <el-popover
+          v-model="copyShow"
+          placement="bottom"
+          trigger="click"
+          title="确定创建组件副本?"
+          popper-class="copy-popover"
+        >
+          <div class="body">
+            <div class="content">
+              点击确定将创建一个当前组件的副本。
+            </div>
+             <div class="button-list">
+               <el-button @click="copyShow=false" size="mini">取消</el-button>
+               <el-button @click="cloneItem(currentData)" type="primary" size="mini">确定</el-button>
+             </div>
+          </div>
+          <el-button slot="reference" type="primary" icon="el-icon-document-copy" circle />
+        </el-popover>
+        <el-button type="primary" @click="save">保存组件</el-button>
+      </div>
+      <div class="body-cont">
+        <div class="body">
+          <div class="tips">
+            组件类型
+          </div>
+          <el-select v-model="currentData.field_type" class="pattern-select" placeholder="选择组件类型">
+            <el-option label="单行输入框组件" value="input" />
+            <el-option label="多行输入框组件" value="textarea" />
+            <el-option label="下拉选择组件" value="select" />
+            <el-option label="多项选择组件" value="checkbox" />
+            <el-option label="单项选择组件" value="radio" />
+            <el-option label="数字输入组件" value="number" />
+            <el-option label="数字滑块组件" value="slider" />
+            <el-option label="时间选择组件" value="time" />
+            <el-option label="时间范围选择组件" value="timeRange" />
+            <el-option label="日期选择组件" value="date" />
+            <el-option label="日期范围选择组件" value="dateRange" />
+          </el-select>
+
+          <div class="tips">
+            表单项介绍
+            <guide
+              video="/static/guide/表单项介绍.jpg"
+              title="表单项介绍"
+              text="展示在输入框上方的大段文本,用于介绍和提示该处应输入的内容。"
+            />
+          </div>
+          <el-input v-model="currentData.field_label" type="textarea" placeholder="请输入表单项介绍" />
+
+          <template>
+            <div class="tips">是否必填</div>
+            <el-switch :active-value="1" :inactive-value="0" v-model="currentData.is_required" />
+          </template>
+
+          <template v-if="['input','select','textarea','time','date','region','email','phone'].includes(currentData.field_type)">
+            <div class="tips">
+              提示文字
+              <guide
+                video="/static/guide/提示文字.jpg"
+                title="提示文字"
+                text="用户输入内容前,展示在输入框中的简短文本,用于提示该处应输入的内容。"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.placeholder" placeholder="请输入提示文字" />
+          </template>
+
+          <template v-if="['textarea'].includes(currentData.field_type)">
+            <div class="tips">
+              行数范围
+            </div>
+            <el-slider v-model="currentData.field_data.rows" class="slider" range :max="12" :min="1" />
+          </template>
+
+          <template v-if="['input','textarea'].includes(currentData.field_type)">
+            <div class="tips">验证规则</div>
+            <el-input v-model="currentData.field_data.pattern" :rows="3" type="textarea" placeholder="规则正则表达式" />
+            <el-select v-model="currentData.field_data.pattern" class="pattern-select" placeholder="选择预设规则">
+              <el-option label="不做判断" value="" />
+              <el-option label="国内手机号" value="^1\d{10}$" />
+              <el-option label="国内身份证号码" value="^[1-9]\d{5}(19\d{2}|20\d{2})((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\d)|(3[0-1]))\d{3}(\d|X|x)$" />
+              <el-option label="电子邮箱" value="^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$" />
+            </el-select>
+          </template>
+
+          <template v-if="currentData.field_type==='region'">
+            <div class="tips">
+              地区范围
+            </div>
+            <el-radio-group v-model="currentData.field_data.range" size="small">
+              <el-radio-button :label="2">省/市</el-radio-button>
+              <el-radio-button :label="3">省/市/县</el-radio-button>
+            </el-radio-group>
+          </template>
+
+          <template v-if="['timeRange','dateRange'].includes(currentData.field_type)">
+            <div class="tips">
+              起始提示文字
+              <guide
+                video="/static/guide/起始提示文字.jpg"
+                title="起始提示文字"
+                text="时间与日期类型组件输入内容前,显示在开始时间输入框中,用于提示的文字内容"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.placeholder" placeholder="请输入起始提示文字" />
+            <div class="tips">
+              结束提示文字
+              <guide
+                video="/static/guide/结束提示文字.jpg"
+                title="结束提示文字"
+                text="时间与日期类型组件输入内容前,显示在结束时间输入框中,用于提示的文字内容"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.endPlaceholder" placeholder="请输入结束提示文字" />
+            <div class="tips">
+              范围分隔符
+              <guide
+                video="/static/guide/范围分隔符.jpg"
+                title="范围分隔符"
+                text="时间与日期类型组件中,开始与结束提示文字中间的分隔文字。"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.rangeSeparator" placeholder="请输入范围分隔符" />
+          </template>
+
+          <template v-if="['select','radio','checkbox'].includes(currentData.field_type)">
+            <div class="tips">
+              选项设定
+              <guide
+                video="/static/guide/选项设定.mp4"
+                title="选项设定"
+                text="在单选、多选、选择器组件中,编辑可供用户选择的选择项目。"
+              />
+            </div>
+            <div class="select-list">
+              <div class="item">
+                <draggable v-model="currentData.field_data.options" :options="{sort:true,animation:300}">
+                  <transition-group class="select-inner">
+                    <el-input v-for="(item,index) in currentData.field_data.options" :key="item.value" :value="item.label" disabled>
+                      <div slot="prefix" class="handel">⠿</div>
+                      <el-button slot="append" type="danger" icon="el-icon-delete" @click="removeSelectItem(index)" />
+                    </el-input>
+                  </transition-group>
+                </draggable>
+                <el-input v-model="selectInput" placeholder="请输入选项" @keyup.enter.native="addSelectItem">
+                  <el-button slot="append" icon="el-icon-plus" @click="addSelectItem" />
+                </el-input>
+              </div>
+            </div>
+          </template>
+
+          <template v-if="['number','slider'].includes(currentData.field_type)">
+            <div class="tips">
+              最小值
+              <guide
+                video="/static/guide/最小值.jpg"
+                title="最小值"
+                text="数字输入类型的组件中,限制可以输入的最小数字。"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.min" @change="currentData.field_data.value=$event-0" />
+            <div class="tips">
+              最大值
+              <guide
+                video="/static/guide/最大值.jpg"
+                title="最大值"
+                text="数字输入类型的组件中,限制可以输入的最大数字。"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.max" />
+            <div class="tips">
+              步进值
+              <guide
+                video="/static/guide/步进值.mp4"
+                title="步进值"
+                text="数字输入类型的组件中,限制输入的数字为多少的倍数。"
+              />
+            </div>
+            <el-input v-model="currentData.field_data.step" />
+          </template>
+
+          <div class="tips">
+            组件宽度
+            <guide
+              video="/static/guide/组件宽度.mp4"
+              title="组件宽度"
+              text="设置所选定组件的宽度,有1/3、1/2、2/3、占据全部宽度,总共四种尺寸供选择。"
+            />
+          </div>
+          <el-radio-group v-model="currentData.field_data.width" size="small">
+            <el-radio-button :label="2">窄</el-radio-button>
+            <el-radio-button :label="3">中</el-radio-button>
+            <el-radio-button :label="4">宽</el-radio-button>
+            <el-radio-button :label="6">长</el-radio-button>
+          </el-radio-group>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.main-box{
+  position: relative;
+  overflow: hidden;
+  padding: 0 !important;
+  height: 100%;
+  width: 100%;
+  display: grid;
+  grid-template-columns: 360px 1fr 360px;
+  grid-template-rows: 1fr;
+  .form-item{
+    background: white;
+    margin-bottom: -1px;
+    border-top: 1px solid lightgrey;
+    border-bottom: 1px solid lightgrey;
+    padding: 24px;
+    &.active{
+      animation: 300ms current linear infinite alternate;
+    }
+    @keyframes current {
+      0%{
+        box-shadow: inset 0 0 0 3px #2563EBFF;
+      }
+      100%{
+        box-shadow: inset 0 0 0 3px #2563EB88;
+      }
+    }
+    .tips{
+      font-size: 15px;
+      color: #989898;
+      margin-bottom: 8px;
+    }
+    &.email{
+      .code-input{
+        display: flex;
+        grid-gap: 8px;
+        margin-top: 8px;
+      }
+    }
+    &.phone{
+      .phone-input{
+        display: flex;
+        grid-gap: 8px;
+      }
+    }
+    &.select{
+      .el-select{
+        width: 100%;
+      }
+    }
+    &.number{
+      .el-input-number{
+        width: 100%;
+      }
+    }
+    &.time,&.timeRange{
+      .el-date-editor{
+        width: 100%;
+      }
+    }
+    &.date,&.dateRange{
+      .el-date-editor{
+        width: 100%;
+      }
+    }
+    &.region{
+      .el-cascader{
+        width: 100%;
+      }
+    }
+    &.view{
+      transition-duration: 300ms;
+      border-radius: 8px;
+      border: 3px solid #2563EB00;
+      padding: 12px 12px;
+      margin-bottom: 0;
+      &.tips{
+        padding: 12px 12px;
+      }
+      &.active{
+        border: 3px solid #2563EB;
+      }
+    }
+  }
+  .comp-lib{
+    z-index: 1;
+    position: relative;
+    display: grid;
+    grid-template-rows: auto 1fr;
+    box-shadow: 0 1px 4px 0 #00000022;
+    .add{
+      position: absolute;
+      right: 32px;
+      bottom: 32px;
+      width: 56px;
+      height: 56px;
+      color: white;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      background-color: #2563EB;
+      border-radius: 50%;
+      font-size: 28px;
+      cursor: pointer;
+      z-index: 2;
+      transition-duration: 300ms;
+      &::after{
+        content: '添加组件';
+        position: absolute;
+        display: block;
+        padding: 8px 16px;
+        border-radius: 8px;
+        right: 68px;
+        text-align: center;
+        pointer-events: none;
+        opacity: 0;
+        transition-duration: 300ms;
+        color: white;
+        font-size: 16px;
+        width: 70px;
+        background-color: #409EFF;
+      }
+      &:hover{
+        box-shadow: 2px 2px 8px 2px #00000022;
+        scale: 1.04;
+        &::after{
+          opacity: 1;
+        }
+      }
+    }
+    .title{
+      padding: 24px;
+    }
+    .list{
+      position: relative;
+      .drag-list{
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 100%;
+        width: 100%;
+        overflow: hidden;
+        overflow-y: auto;
+        .drag-cont{
+          width: 100%;
+          height: 100%;
+          display: block;
+        }
+      }
+    }
+  }
+  .form-view{
+    position: relative;
+    background: #F9FAFB;
+    .scroll-view{
+      position: absolute;
+      left: 0;
+      top: 0;
+      height: 100%;
+      width: 100%;
+      overflow: hidden;
+      .form-cont{
+        padding: 12px;
+        border-radius: 16px;
+        margin: 24px;
+        background: white;
+        width: calc(100% - 48px);
+        box-shadow: 0 1px 4px 0 #00000022;
+        .form-head{
+          padding: 12px;
+          border-radius: 8px;
+          transition-duration: 300ms;
+          border: 3px solid #2563EB00;
+          .title{
+            font-size: 36px;
+            font-weight: bold;
+          }
+          .tips{
+            font-size: 20px;
+            color: #989898;
+            margin-bottom: 6px;
+          }
+        }
+        .form-body{
+          .drag-cont{
+            display: grid;
+            grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
+            width: 100%;
+            align-content: start;
+            .form-item{
+              outline: 3px dashed lightgray;
+              height: fit-content;
+            }
+            .required{
+              .tips{
+                &::after{
+                  margin-left: 4px;
+                  content: '*';
+                  color: red;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  .comp-edit{
+    z-index: 1;
+    position: relative;
+    box-shadow: 0 1px 4px 0 #00000022;
+    display: grid;
+    grid-template-rows: auto 1fr;
+    .body-cont{
+      position: relative;
+      height: calc(100% - 16px);
+      width: 100%;
+    }
+    .body{
+      position: absolute;
+      overflow: hidden;
+      overflow-y: auto;
+      top: 0;
+      left: 0;
+      height: 100%;
+      width: 100%;
+      padding: 0 16px;
+      display: flex;
+      flex-direction: column;
+      align-items: flex-end;
+      .tips{
+        width: 100%;
+        font-size: 16px;
+        color: #505050;
+        margin-bottom: 6px;
+        margin-top: 12px;
+      }
+      .pattern-select{
+        margin-top: 6px;
+        width: 100%;
+      }
+      .slider{
+        width: 100%;
+      }
+      .select-list{
+        width: 100%;
+        .handel{
+          height: 100%;
+          display: flex;
+          align-items: center;
+        }
+        .select-inner{
+          .el-input__inner{
+            cursor: grab;
+          }
+        }
+      }
+    }
+    .button{
+      gap: 12px;
+      display: flex;
+      justify-content: flex-end;
+      padding: 16px;
+    }
+    .title{
+      padding: 16px;
+      display: flex;
+      align-items: center;
+      .icon{
+        margin-left: -4px;
+        margin-top: -4px;
+        margin-right: 8px;
+        font-size: 24px;
+        cursor: pointer;
+        padding: 4px;
+        border-radius: 8px;
+        transition-duration: 300ms;
+        &:hover{
+          color: #2563EB;
+          background: #ececec;
+        }
+      }
+    }
+  }
+}
+</style>
+<style>
+.copy-popover{
+  .body{
+    .content{
+      margin-bottom: 24px;
+    }
+    .button-list{
+      display: flex;
+      justify-content: flex-end;
+    }
+  }
+}
+</style>

File diff suppressed because it is too large
+ 967 - 236
src/views/preRegManage/edit.vue


+ 768 - 0
src/views/preRegManage/editBack2.vue

@@ -0,0 +1,768 @@
+<script lang="ts">
+import Vue from 'vue'
+import { saveForm, getFormInfo } from '@/api/form'
+import guide from '@/views/components/guidePopover.vue'
+import draggable from 'vuedraggable'
+import countryCode from '@/lib/countryCode.json'
+
+export default Vue.extend({
+  name: 'Edit',
+  components: {
+    draggable,
+    guide
+  },
+  data() {
+    return {
+      compList: [
+        {
+          type: 'input',
+          name: 'first_name',
+          label: '请在下方输入名字',
+          value: '',
+          width: 3,
+          placeholder: '请输入名字',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'last_name',
+          label: '请在下方输入姓氏',
+          value: '',
+          width: 3,
+          placeholder: '请输入姓氏',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'id_type',
+          label: '请在下方选择证件类型',
+          value: '',
+          width: 2,
+          options: [
+            { label: '身份证', value: 'id' },
+            { label: '护照', value: 'passport' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'id_number',
+          label: '请在下方输入证件号码',
+          value: '',
+          width: 4,
+          placeholder: '请输入证件号码',
+          required: false
+        },
+        {
+          type: 'phone',
+          name: 'mobile_country_code',
+          label: '请在下方输入手机国家代码',
+          value: '',
+          country: 'CN',
+          width: 2,
+          placeholder: '请输入手机号',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'mobile',
+          label: '请在下方输入手机号码',
+          value: '',
+          country: 'CN',
+          width: 4,
+          placeholder: '请输入手机号',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'email',
+          label: '请在下方输入邮箱',
+          value: '',
+          width: 6,
+          placeholder: '请输入邮箱',
+          codePlaceholder: '请输入验证码',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'company',
+          label: '请在下方输入公司名称',
+          value: '',
+          width: 2,
+          placeholder: '请输入公司名称',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'department',
+          label: '请在下方输入部门名称',
+          value: '',
+          width: 2,
+          placeholder: '请输入部门名称',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'position',
+          label: '请在下方选择职位',
+          value: '',
+          width: 2,
+          options: [
+            { label: '职位', value: '职位' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'country',
+          label: '请在下方选择国家',
+          value: '',
+          width: 2,
+          options: [
+            { label: '国家', value: '国家' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'province',
+          label: '请在下方选择省份',
+          value: '',
+          width: 2,
+          options: [
+            { label: '省份', value: '省份' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'city',
+          label: '请在下方选择市',
+          value: '',
+          width: 2,
+          options: [
+            { label: '市', value: '市' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'address',
+          label: '请在下方输入地址',
+          value: '',
+          width: 6,
+          placeholder: '请输入地址',
+          required: false
+        },
+        {
+          type: 'select',
+          name: 'industry',
+          label: '请在下方选择行业',
+          value: '',
+          width: 2,
+          options: [
+            { label: '行业', value: '行业' }
+          ],
+          placeholder: '请选择',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'interested_products',
+          label: '感兴趣的产品',
+          value: '',
+          width: 3,
+          placeholder: '请输入地址',
+          required: false
+        },
+        {
+          type: 'input',
+          name: 'business_type',
+          label: '业务性质',
+          value: '',
+          width: 3,
+          placeholder: '请输入地址',
+          required: false
+        },
+      ],
+      formData: [],
+      trashData: [],
+      formInfo: {
+        name: '未命名表单',
+        desc: '从右侧拖动组件到表单区域',
+        id: ''
+      },
+      isDrag: false,
+      currentKey: '',
+      currentData: {},
+      hoverKey: '',
+      selectInput: '',
+      countryCode,
+      loading: false
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  methods: {
+    init() {
+      function getType(name) {
+        if (['first_name', 'last_name', 'email' ,'id_number', 'company', 'department', 'address','country','interested_products','business_type','mobile'].includes(name)) {
+          return 'input'
+        }
+        if (['id_type', 'position', 'province', 'city', 'industry'].includes(name)) {
+          return 'select'
+        }
+        if (['mobile_country_code'].includes(name)) {
+          return 'phone'
+        }
+      }
+      if (this.$route.params.id) {
+        this.loading = true
+        this.formInfo.id = this.$route.params.id
+        getFormInfo(this.formInfo.id).then(res => {
+          this.loading = false
+          this.formInfo.name = res.data.template_name
+          this.formInfo.desc = res.data.description
+          res.data.fields.forEach(fields => {
+            this.compList.forEach(item => {
+              if (fields.field_name === item.name) {
+                this.formData.push({
+                  key: fields.id,
+                  type: getType(fields.field_name),
+                  name: fields.field_name,
+                  label: fields.field_label,
+                  value: '',
+                  width: item.width,
+                  placeholder: '请输入',
+                  required: fields.is_required ? 1 : 0
+                })
+              }
+            })
+          })
+          console.log(res)
+        }).catch(err => {
+          this.loading = false
+        })
+      }
+    },
+    save() {
+      if (this.loading) {
+        return
+      }
+      this.loading = true
+      const saveData = []
+      this.formData.forEach(item => {
+        saveData.push({
+          field_name: item.name,
+          field_label: item.label,
+          is_required: item.required? 1 : 0
+        })
+      })
+      saveForm(this.formInfo.id, this.formInfo.name, this.formInfo.desc, saveData).then(res => {
+        this.loading = false
+        this.$message.success('保存成功')
+        this.$router.push('/preRegister/list')
+        console.log(res)
+      }).catch(err => {
+        this.loading = false
+        this.$message.error('保存失败')
+        console.log(err)
+      })
+    },
+    choseComp(element) {
+      this.currentKey = element.key
+      this.currentData = element
+    },
+    removeComp(element) {
+      this.$confirm('确定删除吗?', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+        callback: action => {
+          if (action === 'confirm') {
+            const index = this.getIndexByKey(element.key)
+            this.formData.splice(index, 1)
+            this.currentKey = ''
+          }
+        }
+      })
+    },
+    addSelectItem() {
+      if (this.selectInput.length === 0) { return }
+      this.currentData.options.push({
+        label: this.selectInput,
+        value: this.selectInput
+      })
+      this.selectInput = ''
+    },
+    removeSelectItem(index) {
+      this.currentData.options.splice(index, 1)
+    },
+    cloneItem(item) {
+      const newItem = JSON.parse(JSON.stringify(item))
+      newItem.key = newItem.type + new Date().getTime()
+      return newItem
+    },
+    removeTrash(element) {
+      const index = this.getIndexByKey(element.key)
+      this.trashData.splice(index, 1)
+    },
+    hover(element) {
+      if (this.isDrag) return
+      this.hoverKey = element.key
+    },
+    blur(element) {
+      this.hoverKey = ''
+    },
+    getIndexByKey(key) {
+      return this.formData.indexOf(this.formData.find(item => item.key === key))
+    },
+    handleStart() {
+      this.isDrag = true
+      this.formData.forEach(item => {
+        item.hover = false
+      })
+    },
+    handleEnd() {
+      this.isDrag = false
+    }
+  }
+})
+</script>
+
+<template>
+  <div v-loading="loading" class="main-box">
+    <div class="comp-lib">
+      <div class="title">
+        组件库
+      </div>
+      <div class="list">
+        <draggable v-model="compList" :options="{sort:false}" :group="{name:'form',put:false,pull:'clone'}" :clone="cloneItem" class="drag-list">
+          <transition-group class="drag-cont">
+            <div v-for="(element) in compList" :key="element.name">
+              <div v-if="element.type==='input'" :class="[element.type,'form-item']">
+                <div class="tips">{{ element.label }}</div>
+                <el-input :value="element.value" :placeholder="element.placeholder" />
+              </div>
+              <div v-if="element.type==='phone'" :class="[element.type,'form-item']">
+                <div class="tips">{{ element.label }}</div>
+                <div class="phone-input">
+                  <el-select v-model="element.country" :placeholder="element.placeholder">
+                    <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.country_code" />
+                  </el-select>
+                </div>
+              </div>
+              <div v-if="element.type==='select'" :class="[element.type,'form-item']">
+                <div class="tips">{{ element.label }}</div>
+                <el-select :value="element.value" :placeholder="element.placeholder">
+                  <el-option v-for="item in element.options" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </div>
+            </div>
+          </transition-group>
+        </draggable>
+      </div>
+    </div>
+    <div class="form-view">
+      <div class="scroll-view">
+        <div class="form-cont">
+          <div :class="['form-head',getIndexByKey(currentKey) === -1?'active':'']" @click="currentKey=''">
+            <div class="title">{{ formInfo.name }}</div>
+            <div class="tips">{{ formInfo.desc }}</div>
+          </div>
+          <draggable v-model="formData" :group="{name:'form'}" :options="{sort:true,animation:300}" class="form-body" @start="handleStart" @end="handleEnd">
+            <transition-group class="drag-cont">
+              <div v-for="element in formData" :key="element.key" :style="{gridColumn:'span '+element.width}" :class="[element.required?'required':'']" @click="choseComp(element)" @mouseenter="hover(element)" @mouseleave="blur(element)">
+                <div v-if="element.type==='input'" :class="[element.type,'form-item','view',{'active':element.key===hoverKey||element.key===currentKey}]">
+                  <div class="tips">{{ element.label }}</div>
+                  <el-input :value="element.value" :placeholder="element.placeholder" />
+                </div>
+                <div v-if="element.type==='email'" :class="[element.type,'form-item','view',{'active':element.key===hoverKey||element.key===currentKey}]">
+                  <div class="tips">{{ element.label }}</div>
+                  <div>
+                    <el-input v-model="element.value" :placeholder="element.placeholder" />
+                  </div>
+                  <div class="code-input">
+                    <el-input v-model="element.value" :placeholder="element.codePlaceholder" />
+                    <el-button>获取验证码</el-button>
+                  </div>
+                </div>
+                <div v-if="element.type==='phone'" :class="[element.type,'form-item','view',{'active':element.key===hoverKey||element.key===currentKey}]">
+                  <div class="tips">{{ element.label }}</div>
+                  <div class="phone-input">
+                    <el-select v-model="element.country" :placeholder="element.placeholder">
+                      <el-option v-for="(item,index) in countryCode" :key="item.country_code+index" :label="'+'+item.phone_code+'('+item.chinese_name+')'" :value="item.country_code" />
+                    </el-select>
+                  </div>
+                </div>
+                <div v-if="element.type==='select'" :class="[element.type,'form-item','view',{'active':element.key===hoverKey||element.key===currentKey}]">
+                  <div class="tips">{{ element.label }}</div>
+                  <el-select :value="element.value" :placeholder="element.placeholder">
+                    <el-option v-for="item in element.options" :key="item.value" :label="item.label" :value="item.value" />
+                  </el-select>
+                </div>
+              </div>
+            </transition-group>
+          </draggable>
+        </div>
+      </div>
+    </div>
+    <div class="comp-edit">
+      <template v-if="getIndexByKey(currentKey) === -1">
+        <div class="button">
+          <el-button type="primary" @click="save">保存表单</el-button>
+        </div>
+        <div class="title">表单设定</div>
+        <div class="body">
+          <div class="tips">表单名称</div>
+          <el-input v-model="formInfo.name" placeholder="请输入表单名称" />
+          <div class="tips">表单介绍</div>
+          <el-input v-model="formInfo.desc" type="textarea" placeholder="请输入表单介绍" />
+        </div>
+      </template>
+      <template v-else>
+        <div />
+        <div class="title">
+          <span class="el-icon-arrow-left icon" @click="currentKey=''" />
+          组件设定
+        </div>
+        <div class="body">
+
+          <div class="tips">
+            表单项介绍
+            <guide
+              video="/static/guide/表单项介绍.jpg"
+              title="表单项介绍"
+              text="展示在输入框上方的大段文本,用于介绍和提示该处应输入的内容。"
+            />
+          </div>
+          <el-input v-model="currentData.label" type="textarea" placeholder="请输入表单项介绍" />
+
+          <template>
+            <div class="tips">是否必填</div>
+            <el-switch v-model="currentData.required" />
+          </template>
+
+        </div>
+        <div class="button">
+          <el-button type="danger" @click="removeComp(currentData)">删除组件</el-button>
+        </div>
+      </template>
+    </div>
+    <div :class="['trash-bin',isDrag?'':'hide']">
+      <div class="trash-inner">
+        <draggable v-model="trashData" :group="{name:'form'}" :options="{sort:false,animation:300}" :class="['trash-cont',trashData.length?'':'hide']">
+          <transition-group class="drag-cont">
+            <div v-for="element in trashData" :key="element.key" class="form-item view">
+              <div class="tips">{{ element.label }}</div>
+              <div class="del el-icon-delete" @click="removeTrash(element)" />
+            </div>
+          </transition-group>
+        </draggable>
+      </div>
+      <div class="text">回收站</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+  .main-box{
+    position: relative;
+    overflow: hidden;
+    padding: 0 !important;
+    height: 100%;
+    width: 100%;
+    display: grid;
+    grid-template-columns: 360px 1fr 360px;
+    grid-template-rows: 1fr;
+    .form-item{
+      cursor: grab;
+      background: white;
+      margin-bottom: -1px;
+      border-top: 1px solid lightgrey;
+      border-bottom: 1px solid lightgrey;
+      padding: 24px;
+      .tips{
+        font-size: 15px;
+        color: #989898;
+        margin-bottom: 8px;
+      }
+      &.email{
+        .code-input{
+          display: flex;
+          grid-gap: 8px;
+          margin-top: 8px;
+        }
+      }
+      &.phone{
+        .el-select{
+          display: flex;
+          grid-gap: 8px;
+        }
+      }
+      &.select{
+        .el-select{
+          width: 100%;
+        }
+      }
+      &.number{
+        .el-input-number{
+          width: 100%;
+        }
+      }
+      &.time,&.timeRange{
+        .el-date-editor{
+          width: 100%;
+        }
+      }
+      &.date,&.dateRange{
+        .el-date-editor{
+          width: 100%;
+        }
+      }
+      &.region{
+        .el-cascader{
+          width: 100%;
+        }
+      }
+      &.view{
+        transition-duration: 300ms;
+        border-radius: 8px;
+        border: 3px solid #2563EB00;
+        padding: 12px 12px;
+        margin-bottom: 0;
+        &.tips{
+          padding: 12px 12px;
+        }
+        &.active{
+          border: 3px solid #2563EB;
+        }
+      }
+    }
+    .trash-bin{
+      box-shadow: -4px 8px 12px 0 #00000044;
+      padding: 8px;
+      position: absolute;
+      border-radius: 32px 32px 0 0;
+      z-index: 3;
+      right: 200px;
+      bottom: 0;
+      width: 360px;
+      height: 150px;
+      background: lightgrey;
+      transition-duration: 300ms;
+      .trash-inner{
+        position: relative;
+        outline: #e4e4e4 5px solid;
+        border-radius: 24px;
+        width: 100%;
+        height: 60px;
+        background-image: linear-gradient( #b3b3b3, #2b2b2b);
+        .trash-cont{
+          overflow: hidden;
+          border-radius: 24px;
+          position: absolute;
+          width: 100%;
+          height: 200%;
+          left: 0;
+          bottom: 0;
+          &.hide{
+            height: 100%;
+          }
+          .drag-cont{
+            padding: 6px 12px 0;
+            overflow: hidden;
+            overflow-y: scroll;
+            display: block;
+            width: calc(100% + 20px);
+            height: 100%;
+            .form-item{
+              position: sticky;
+              left: 0;
+              top: 0;
+              border-bottom-right-radius: 0;
+              border-bottom-left-radius: 0;
+               border: 1px solid lightgrey;
+              height: 100%;
+              .del{
+                position: absolute;
+                right: 16px;
+                top: 16px;
+                cursor: pointer;
+              }
+            }
+          }
+        }
+      }
+      .text{
+        width: 100%;
+        text-align: center;
+        margin-top: 16px;
+        font-size: 36px;
+        font-weight: bold;
+        color: gray;
+        text-shadow: 4px 4px 4px 4px #ffffff;
+      }
+      &.hide{
+        bottom: -160px;
+      }
+      &:hover{
+        bottom: -60px;
+      }
+    }
+    .comp-lib{
+      z-index: 1;
+      position: relative;
+      display: grid;
+      grid-template-rows: auto 1fr;
+      box-shadow: 0 1px 4px 0 #00000022;
+      .title{
+        padding: 24px;
+      }
+      .list{
+        position: relative;
+        .drag-list{
+          position: absolute;
+          top: 0;
+          left: 0;
+          height: 100%;
+          width: 100%;
+          overflow: hidden;
+          overflow-y: auto;
+          .drag-cont{
+            width: 100%;
+            height: 100%;
+            display: block;
+          }
+        }
+      }
+    }
+    .form-view{
+      position: relative;
+      background: #F9FAFB;
+      .scroll-view{
+        position: absolute;
+        left: 0;
+        top: 0;
+        height: 100%;
+        width: 100%;
+        overflow: hidden;
+        overflow-y: scroll;
+        .form-cont{
+          padding: 12px;
+          border-radius: 16px;
+          margin: 24px;
+          background: white;
+          width: calc(100% - 48px);
+          box-shadow: 0 1px 4px 0 #00000022;
+          .form-head{
+            padding: 12px;
+            border-radius: 8px;
+            transition-duration: 300ms;
+            border: 3px solid #2563EB00;
+            &:hover,&.active{
+              border: 3px solid #2563EB;
+            }
+            .title{
+              font-size: 36px;
+              font-weight: bold;
+            }
+            .tips{
+              font-size: 20px;
+              color: #989898;
+              margin-bottom: 6px;
+            }
+          }
+          .form-body{
+            .drag-cont{
+              display: grid;
+              grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
+              width: 100%;
+              min-height: 300px;
+              align-content: start;
+              .form-item{
+                height: fit-content;
+              }
+              .required{
+                .tips{
+                  &::after{
+                    margin-left: 4px;
+                    content: '*';
+                    color: red;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    .comp-edit{
+      z-index: 1;
+      position: relative;
+      box-shadow: 0 1px 4px 0 #00000022;
+      display: grid;
+      grid-template-rows: auto auto 1fr auto;
+      .body{
+        padding: 0 16px;
+        display: flex;
+        flex-direction: column;
+        align-items: flex-end;
+        .tips{
+          width: 100%;
+          font-size: 16px;
+          color: #505050;
+          margin-bottom: 6px;
+          margin-top: 12px;
+        }
+        .pattern-select{
+          margin-top: 6px;
+          width: 100%;
+        }
+        .select-list{
+          width: 100%;
+          .handel{
+            height: 100%;
+            display: flex;
+            align-items: center;
+          }
+          .select-inner{
+            .el-input__inner{
+              cursor: grab;
+            }
+          }
+        }
+      }
+      .button{
+        display: flex;
+        justify-content: flex-end;
+        padding: 16px;
+      }
+      .title{
+        padding: 16px;
+        display: flex;
+        align-items: center;
+        .icon{
+          margin-left: -4px;
+          margin-top: -4px;
+          margin-right: 8px;
+          font-size: 24px;
+          cursor: pointer;
+          padding: 4px;
+          border-radius: 8px;
+          transition-duration: 300ms;
+          &:hover{
+            color: #2563EB;
+            background: #ececec;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 56 - 7
src/views/user/form.vue

@@ -38,6 +38,11 @@ export default Vue.extend({
       if (this.token) {
         getExpoInfo(this.expo_key).then(res => {
           this.from_data = res.data
+          this.from_data.form_fields.forEach(item => {
+            if (item.field_type === 'checkbox') {
+              this.$set(item, 'value', [])
+            }
+          })
           this.expo_id = res.data.id
         }).catch(err => {
           console.log('err')
@@ -80,13 +85,18 @@ export default Vue.extend({
       })
     },
     submit() {
-      if(this.loading) {
+      if (this.loading) {
         return
       }
       this.loading = true
-      let form_data = {}
+      const form_data = {}
       for (let i = 0; i < this.from_data.form_fields.length; i++) {
-        form_data[this.from_data.form_fields[i].field_name] = this.from_data.form_fields[i].value
+        if (this.from_data.form_fields[i].field_name) {
+          form_data[this.from_data.form_fields[i].field_name] = this.from_data.form_fields[i].value
+        } else {
+          form_data['field_' + this.from_data.form_fields[i].id] = this.from_data.form_fields[i].value
+        }
+
         if (this.from_data.form_fields[i].is_required && !this.from_data.form_fields[i].value) {
           this.$message.error('请填写必填项')
           this.loading = false
@@ -128,11 +138,11 @@ export default Vue.extend({
         {{ from_data.content }}
       </div>
       <div v-if="isOk" class="form-body ok">
-        <div class="icon el-icon-success"></div>
+        <div class="icon el-icon-success" />
         <div class="text">表单提交完成</div>
       </div>
       <div v-else class="form-body">
-        <div v-for="item in from_data.form_fields" :key="item.id" :style="{gridColumn:'span '+returnWidth(item.field_name)}" class="form-item">
+        <div v-for="item in from_data.form_fields" :key="item.id" :style="{gridColumn:'span '+item.field_data.width}" class="form-item">
           <div class="label" :class="item.is_required?'required':''">{{ item.field_label }}</div>
           <el-select v-if="item.field_name==='id_type'" v-model="item.value">
             <el-option value="Passport" label="护照" />
@@ -218,20 +228,51 @@ export default Vue.extend({
             </div>
             <el-input slot="reference" :value="item.value" />
           </el-popover>
+          <template v-else-if="item.field_name===''">
+            <el-input v-if="item.field_type==='input'" v-model="item.value" :placeholder="item.field_data.placeholder" />
+
+            <el-select v-if="item.field_type==='select'" v-model="item.value">
+              <option v-for="option in item.field_data.options" :key="option.value" :value="option.value">
+                {{ option.label }}
+              </option>
+            </el-select>
+
+            <el-checkbox-group v-if="item.field_type==='checkbox'" v-model="item.value">
+              <el-checkbox v-for="option in item.field_data.options" :key="option.value" :label="option.label" />
+            </el-checkbox-group>
+
+            <el-radio-group v-if="item.field_type==='radio'" v-model="item.value">
+              <el-radio v-for="option in item.field_data.options" :key="option.value" :label="option.label" />
+            </el-radio-group>
+
+            <el-input-number v-if="item.field_type==='number'" v-model="item.value" :max="item.field_data.max" :min="item.field_data.min" :step="item.field_data.step" />
+
+            <el-slider v-if="item.field_type==='slider'" v-model="item.value" :max="item.field_data.max" :min="item.field_data.min" :step="item.field_data.step" />
+
+            <el-input v-if="item.field_type==='textarea'" v-model="item.value" type="textarea" :autosize="{ minRows: item.field_data.minRows, maxRows: item.field_data.maxRows}" :placeholder="item.field_data.placeholder" />
+
+            <el-time-picker v-if="item.field_type==='time'" v-model="item.value" :placeholder="item.field_data.placeholder" />
+
+            <el-time-picker v-if="item.field_type==='timeRange'" v-model="item.value" is-range :start-placeholder="item.field_data.placeholder" :end-placeholder="item.field_data.endPlaceholder" :range-separator="item.field_data.rangeSeparator" />
+
+            <el-date-picker v-if="item.field_type==='date'" v-model="item.value" type="date" :placeholder="item.field_data.placeholder" />
+
+            <el-date-picker v-if="item.field_type==='dateRange'" v-model="item.value" type="daterange" :start-placeholder="item.field_data.placeholder" :end-placeholder="item.field_data.endPlaceholder" :range-separator="item.field_data.rangeSeparator" />
+          </template>
           <el-input v-else v-model="item.value" />
         </div>
       </div>
       <div v-if="!isOk" class="button">
         <el-button :disabled="loading" type="primary" @click="submit()">
           提交表单
-          <span v-if="loading" class="el-icon-loading"></span>
+          <span v-if="loading" class="el-icon-loading" />
         </el-button>
       </div>
     </div>
   </div>
 </template>
 
-<style scoped>
+<style scoped lang="scss">
 .body{
   margin: 36px auto;
   max-width: 800px;
@@ -270,6 +311,9 @@ export default Vue.extend({
         }
       }
       .form-item{
+        div{
+          width: 100%;
+        }
         .label{
           font-size: 15px;
           color: gray;
@@ -318,3 +362,8 @@ body{
   }
 }
 </style>
+<style>
+#app{
+  overflow: auto !important;
+}
+</style>