├── static ├── .gitkeep ├── label.1.json ├── label.17.json ├── label.3.json ├── label.12.json ├── label.19.json ├── label.5.json ├── label.14.json ├── label.8.json └── label.json ├── config ├── prod.env.js ├── dev.env.js ├── proxyConfig.js └── index.js ├── .gitattributes ├── .editorconfig ├── .gitignore ├── src ├── components │ ├── custom_form │ │ ├── config │ │ │ └── trigger.js │ │ ├── control │ │ │ ├── Hr.js │ │ │ ├── P.js │ │ │ ├── Title.js │ │ │ ├── Input.js │ │ │ ├── Uploads.js │ │ │ ├── CheckBox.js │ │ │ ├── Radio.js │ │ │ ├── Text.js │ │ │ ├── DatePicker.js │ │ │ ├── Select.js │ │ │ ├── Cascader.js │ │ │ └── Address.js │ │ ├── index.js │ │ ├── ItemIcon.js │ │ ├── FormList.js │ │ ├── Preview.js │ │ ├── Render.js │ │ └── components │ │ │ └── Uploads │ │ │ └── upload.vue │ ├── preview.vue │ ├── render.vue │ └── index.vue ├── router │ └── index.js ├── App.vue └── main.js ├── .babelrc ├── .postcssrc.js ├── index.html ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=Vue 2 | *.css linguist-language=Vue 3 | *.html linguist-language=Vue -------------------------------------------------------------------------------- /static/label.1.json: -------------------------------------------------------------------------------- 1 | {"items":[{"label_value":null,"label_name":""}],"status":"success","code":200,"name":"name"} -------------------------------------------------------------------------------- /static/label.17.json: -------------------------------------------------------------------------------- 1 | {"items":[{"label_value":null,"label_name":null}],"status":"success","code":200,"name":"case"} -------------------------------------------------------------------------------- /static/label.3.json: -------------------------------------------------------------------------------- 1 | { "items": [{ "label_value": null, "label_name": "" }], "status": "success", "code": 200, "name": "phone" } 2 | -------------------------------------------------------------------------------- /static/label.12.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [{ 3 | "label_value": null, 4 | "label_name": null 5 | }], 6 | "status": "success", 7 | "code": 200, 8 | "name": "photo" 9 | } 10 | -------------------------------------------------------------------------------- /static/label.19.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [{ 3 | "label_value": null, 4 | "label_name": null 5 | }], 6 | "status": "success", 7 | "code": 200, 8 | "name": "address" 9 | } 10 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | .history 16 | -------------------------------------------------------------------------------- /src/components/custom_form/config/trigger.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'blur,change', 3 | select: 'change', 4 | radio: 'change', 5 | checkbox: 'change', 6 | cascader: 'input', 7 | uploads: 'handleUploadsValue', 8 | textarea: 'blur,change', 9 | } 10 | -------------------------------------------------------------------------------- /static/label.5.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [{ 3 | "label_value": "1", 4 | "label_name": "男" 5 | }, 6 | { 7 | "label_value": "2", 8 | "label_name": "女" 9 | } 10 | ], 11 | "status": "success", 12 | "code": 200, 13 | "name": "sex" 14 | } -------------------------------------------------------------------------------- /static/label.14.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [{ 3 | "label_value": "1", 4 | "label_name": "是" 5 | }, 6 | { 7 | "label_value": "2", 8 | "label_name": "否" 9 | } 10 | ], 11 | "status": "success", 12 | "code": 200, 13 | "name": "is_case" 14 | } 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /config/proxyConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | proxy: { 3 | '/apis': { //将www.exaple.com印射为/apis 4 | target: 'http://workflow.test', // 接口域名 5 | changeOrigin: true, //是否跨域 6 | pathRewrite: { 7 | '^/apis': '' //需要rewrite的, 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-formbuilder 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /static/label.8.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [{ 3 | "label_value": "1", 4 | "label_name": "电话投诉" 5 | }, 6 | { 7 | "label_value": "2", 8 | "label_name": "书信投诉" 9 | }, 10 | { 11 | "label_value": "3", 12 | "label_name": "网络投诉" 13 | } 14 | ], 15 | "status": "success", 16 | "code": 200, 17 | "name": "complaint" 18 | } 19 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Hr.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h('hr', { 4 | style: { 5 | 'margin-bottom': _self.obj.marginBottom + 'px', 6 | 'margin-top': _self.obj.marginTop + 'px', 7 | } 8 | }) 9 | ] 10 | } 11 | 12 | export const hrConf = { 13 | // 是否可配置 14 | config: true, 15 | marginTop: 0, 16 | marginBottom: 24 17 | } 18 | -------------------------------------------------------------------------------- /src/components/preview.vue: -------------------------------------------------------------------------------- 1 | 6 | 19 | -------------------------------------------------------------------------------- /src/components/custom_form/control/P.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h('p', { 4 | style: { 5 | 'margin-bottom': _self.obj.marginBottom + 'px', 6 | 'margin-top': _self.obj.marginTop + 'px', 7 | 'color': _self.obj.color || "#000" 8 | }, 9 | domProps: { 10 | innerHTML: _self.obj.label || "文本标签" 11 | } 12 | }) 13 | ] 14 | } 15 | 16 | export const pConf = { 17 | config: true, 18 | label: '文本标签', 19 | color: '#000', 20 | marginTop: 0, 21 | marginBottom: 24 22 | } 23 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import index from '@/components/index' 4 | import render from '@/components/render' 5 | import preview from '@/components/preview' 6 | 7 | Vue.use(Router) 8 | 9 | export default new Router({ 10 | routes: [{ 11 | path: '/', 12 | name: 'index', 13 | component: index 14 | }, { 15 | path: '/render', 16 | name: 'render', 17 | component: render 18 | }, { 19 | path: '/preview', 20 | name: 'preview', 21 | component: preview 22 | }] 23 | }) 24 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Title.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h('h' + (_self.obj.level || 3), { 4 | style: { 5 | 'margin-bottom': _self.obj.marginBottom + 'px', 6 | 'margin-top': _self.obj.marginTop + 'px', 7 | }, 8 | domProps: { 9 | innerHTML: _self.obj.label || "Title" 10 | } 11 | }) 12 | ] 13 | } 14 | 15 | export let titleConf = { 16 | // 是否可配置 17 | config: true, 18 | // 控件文本显示内容 19 | label: '标题', 20 | // h标签等级(1-6) 21 | level: 3, 22 | hasLine: true, 23 | marginTop: 0, 24 | marginBottom: 24 25 | } 26 | -------------------------------------------------------------------------------- /src/components/custom_form/index.js: -------------------------------------------------------------------------------- 1 | import render from "./Render"; 2 | import uploadCustom from './components/Uploads/upload'; 3 | import preview from './Preview'; 4 | 5 | const customForm = { 6 | render, 7 | uploadCustom, 8 | preview 9 | }; 10 | 11 | const install = function(Vue, opts = {}) { 12 | Vue.component(render.name, render); 13 | Vue.component(uploadCustom.name, uploadCustom); 14 | Vue.component(preview.name, preview); 15 | }; 16 | 17 | if (typeof window !== 'undefined' && window.Vue) { 18 | install(window.Vue); 19 | } 20 | 21 | export default Object.assign(customForm, { install }); 22 | -------------------------------------------------------------------------------- /static/label.json: -------------------------------------------------------------------------------- 1 | { "items": [{ "id": 1, "label": "姓名", "type": "input", "parent_name": null }, { "id": 3, "label": "电话", "type": "input", "parent_name": null }, { "id": 5, "label": "性别", "type": "radio", "parent_name": null }, { "id": 8, "label": "投诉方式", "type": "select", "parent_name": null }, { "id": 12, "label": "上传图片附件", "type": "uploads", "parent_name": null }, { "id": 14, "label": "案件资料是否收齐", "type": "select", "parent_name": null }, { "id": 17, "label": "标识缺少的资料", "type": "input", "parent_name": "is_case" }, { "id": 19, "label": "地址", "type": "address", "parent_name": null }], "status": "success", "code": 200 } 2 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 12 | 36 | -------------------------------------------------------------------------------- /src/components/custom_form/ItemIcon.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | let icons = []; 3 | // 配置按钮 4 | if (!!_self.obj.config) { 5 | icons.push(h('Icon', { 6 | props: { 7 | type: 'gear-a', 8 | }, 9 | nativeOn: { 10 | click() { 11 | _self.$emit('handleConfEle', _self.index); 12 | } 13 | } 14 | })); 15 | } 16 | // 删除按钮 17 | icons.push(h('Icon', { 18 | props: { 19 | type: 'minus-round' 20 | }, 21 | nativeOn: { 22 | click() { 23 | _self.$emit('handleRemoveEle', _self.index); 24 | } 25 | } 26 | })); 27 | const item_icon = h('div', { 28 | class: { 29 | 'item-icon': true 30 | } 31 | }, icons); 32 | return item_icon; 33 | } 34 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import iview from 'iview'; 7 | import 'iview/dist/styles/iview.css' 8 | import cascaderMulti from 'cascader-multi'; 9 | import iviewArea from 'iview-area'; 10 | import custom_form from './components/custom_form'; 11 | 12 | import axios from 'axios'; 13 | 14 | Vue.config.productionTip = false 15 | Vue.use(iview); 16 | Vue.use(cascaderMulti); 17 | Vue.use(custom_form); 18 | Vue.use(iviewArea); 19 | 20 | // axios.defaults.baseURL = '/apis'; 21 | Vue.prototype.$http = axios; 22 | 23 | /* eslint-disable no-new */ 24 | new Vue({ 25 | el: '#app', 26 | router, 27 | components: { App }, 28 | template: '' 29 | }) 30 | -------------------------------------------------------------------------------- /src/components/custom_form/FormList.js: -------------------------------------------------------------------------------- 1 | import { inputConf } from "./control/Input"; 2 | import { selectConf } from "./control/Select"; 3 | import { radioConf } from "./control/Radio"; 4 | import { checkBoxConf } from "./control/CheckBox"; 5 | import { cascaderConf } from "./control/Cascader"; 6 | import { textConf } from "./control/Text"; 7 | import { titleConf } from "./control/Title"; 8 | import { hrConf } from "./control/Hr"; 9 | import { pConf } from "./control/P"; 10 | import { uploadsConf } from './control/Uploads'; 11 | import { datePickerConf } from './control/DatePicker' 12 | import { addressConf } from './control/Address'; 13 | 14 | const formList = { 15 | title: titleConf, 16 | hr: hrConf, 17 | p: pConf, 18 | input: inputConf, 19 | select: selectConf, 20 | radio: radioConf, 21 | checkbox: checkBoxConf, 22 | datepicker: datePickerConf, 23 | cascader: cascaderConf, 24 | address: addressConf, 25 | uploads: uploadsConf, 26 | text: textConf, 27 | }; 28 | let list_arr = []; 29 | for (let i in formList) { 30 | list_arr.push({ 31 | ele: i, 32 | obj: formList[i] 33 | }); 34 | } 35 | export default list_arr; 36 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Input.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h("Input", { 4 | props: { 5 | placeholder: _self.obj.placeholder || "这是一个输入框", 6 | maxlength: parseInt(_self.obj.maxLength) || 20, 7 | value: _self.obj.value || "" 8 | }, 9 | on: { 10 | "on-change": function(val) { 11 | if (!_self.obj.name) { 12 | return false; 13 | } 14 | _self.obj.value = event.currentTarget.value; 15 | _self.$emit('handleChangeVal', val.currentTarget.value) 16 | } 17 | } 18 | }) 19 | ]; 20 | }; 21 | 22 | 23 | export let inputConf = { 24 | // 对应数据库内类型 25 | type: 'input', 26 | // 是否可配置 27 | config: true, 28 | // 控件左侧label内容 29 | label: '输入框', 30 | placeholder: '', 31 | // 是否显示行内元素 32 | inlineBlock: false, 33 | // 是否必填 34 | require: true, 35 | // 最大长度 36 | maxLength: 20, 37 | // 选项内数据 38 | items: [{ "label_value": null, "label_name": "" }], 39 | value: '', 40 | // 表单name 41 | name: '', 42 | // 验证错误提示信息 43 | ruleError: '该字段不能为空', 44 | // 是否关联字段 45 | relation: false, 46 | // 关联字段name 47 | relation_name: '', 48 | // 关联字段value 49 | relation_value: '', 50 | // 是否被渲染 51 | visibility: true 52 | } 53 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Uploads.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h('uploadCustom', { 4 | props: { 5 | multiple: _self.obj.multiple || false, 6 | type: 'drag', //支持拖拽 7 | action: _self.obj.action || "/imageupload", 8 | 'max-size': _self.obj.maxSize || 2048, 9 | defaultList: _self.obj.value, 10 | name: 'photo' 11 | }, 12 | on: { 13 | handleUploadsValue(arr) { 14 | if (!_self.obj.name) { 15 | return false; 16 | } 17 | _self.obj.value = arr; 18 | _self.$emit('handleChangeVal', arr) 19 | } 20 | } 21 | }) 22 | ] 23 | } 24 | 25 | 26 | export const uploadsConf = { 27 | // 对应数据库内类型 28 | type: 'uploads', 29 | // 是否可配置 30 | config: true, 31 | // 上传地址 32 | action: 'http://workflow.test/imageupload', 33 | // 是否必填 34 | require: true, 35 | // 控件左侧label内容 36 | label: '上传控件', 37 | // 上传文件最大限制 38 | maxSize: 2048, 39 | // 绑定的值 40 | value: [], 41 | // 表单name 42 | name: '', 43 | // 验证错误提示信息 44 | ruleError: '请上传图片', 45 | // 是否关联字段 46 | relation: false, 47 | // 关联字段name 48 | relation_name: '', 49 | // 关联字段value 50 | relation_value: '', 51 | // 是否被渲染 52 | visibility: true 53 | } 54 | -------------------------------------------------------------------------------- /src/components/custom_form/control/CheckBox.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h("CheckboxGroup", { 4 | props: { 5 | value: _self.obj.value || [] 6 | }, 7 | on: { 8 | 'on-change' (arr) { 9 | if (!_self.obj.name) { 10 | return false; 11 | } 12 | _self.obj.value = arr; 13 | _self.$emit('handleChangeVal', arr) 14 | } 15 | } 16 | }, _self.obj.items.map(v => { 17 | return h("Checkbox", { 18 | props: { 19 | label: v.label_value 20 | } 21 | }, v.label_name) 22 | })) 23 | ]; 24 | }; 25 | 26 | export let checkBoxConf = { 27 | // 对应数据库内类型 28 | type: 'checkbox', 29 | // 是否可配置 30 | config: true, 31 | // 控件左侧label内容 32 | label: '多选框', 33 | // 是否显示行内元素 34 | inlineBlock: false, 35 | // 是否必填 36 | require: true, 37 | // 绑定的值 38 | value: [], 39 | // 选项内数据 40 | items: [{ "label_value": "1", "label_name": "单选框1" }, { "label_value": "2", "label_name": "单选框2" }], 41 | // 表单name 42 | name: '', 43 | // 验证错误提示信息 44 | ruleError: '该选项不能为空', 45 | // 是否关联字段 46 | relation: false, 47 | // 关联字段name 48 | relation_name: '', 49 | // 关联字段value 50 | relation_value: '', 51 | // 是否被渲染 52 | visibility: true 53 | } 54 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Radio.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h("RadioGroup", { 4 | props: { 5 | value: _self.obj.value || "-1" 6 | }, 7 | on: { 8 | 'on-change' (value) { 9 | if (!_self.obj.name) { 10 | return false; 11 | } 12 | _self.obj = Object.assign(_self.obj, { 13 | value 14 | }); 15 | _self.$emit('handleChangeVal', value) 16 | } 17 | } 18 | }, _self.obj.items.map(v => { 19 | return h("Radio", { 20 | props: { 21 | label: v.label_value 22 | } 23 | }, v.label_name) 24 | })) 25 | ]; 26 | }; 27 | 28 | export let radioConf = { 29 | // 对应数据库内类型 30 | type: 'radio', 31 | // 是否可配置 32 | config: true, 33 | // 控件左侧label内容 34 | label: '单选框', 35 | // 是否显示行内元素 36 | inlineBlock: false, 37 | // 是否必填 38 | require: true, 39 | // 绑定的值 40 | value: '', 41 | // 选项内数据 42 | items: [{ "label_value": "1", "label_name": "单选框1" }, { "label_value": "2", "label_name": "单选框2" }], 43 | // 表单name 44 | name: '', 45 | // 验证错误提示信息 46 | ruleError: '请选择', 47 | // 是否关联字段 48 | relation: false, 49 | // 关联字段name 50 | relation_name: '', 51 | // 关联字段value 52 | relation_value: '', 53 | // 是否被渲染 54 | visibility: true 55 | } 56 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Text.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h("Input", { 4 | attrs: { 5 | type: "textarea", 6 | placeholder: _self.obj.placeholder || "这是一个文本域", 7 | value: _self.obj.value, 8 | maxlength: _self.obj.maxLength || 200 9 | }, 10 | props: { 11 | rows: _self.obj.maxRows || 5 12 | }, 13 | on: { 14 | "on-change" (event) { 15 | if (!_self.obj.name) { 16 | return false; 17 | } 18 | _self.obj.value = event.currentTarget.value; 19 | _self.$emit('handleChangeVal', event.currentTarget.value) 20 | } 21 | } 22 | }) 23 | ]; 24 | }; 25 | 26 | export let textConf = { 27 | // 对应数据库内类型 28 | type: 'textarea', 29 | // 是否可配置 30 | config: true, 31 | // 控件左侧label内容 32 | label: '文本域', 33 | placeholder: '', 34 | // 是否显示行内元素 35 | inlineBlock: false, 36 | // 最大长度 37 | maxLength: 200, 38 | // 是否必填 39 | require: true, 40 | // 文本域行高 41 | maxRows: 5, 42 | // 绑定的值 43 | value: "", 44 | // 表单name 45 | name: '', 46 | // 验证错误提示信息 47 | ruleError: '该字段不能为空', 48 | // 是否关联字段 49 | relation: false, 50 | // 关联字段name 51 | relation_name: '', 52 | // 关联字段value 53 | relation_value: '', 54 | // 是否被渲染 55 | visibility: true 56 | } 57 | -------------------------------------------------------------------------------- /src/components/custom_form/control/DatePicker.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | export default (_self, h) => { 3 | return [ 4 | h('DatePicker', { 5 | props: { 6 | placeholder: _self.obj.placeholder || (_self.obj.name ? "" : "请选择日期"), 7 | type: (!_self.obj.format || _self.obj.format == 'yyyy年MM月dd日') ? 'date' : 'datetime', 8 | format: _self.obj.format || 'yyyy年MM月dd日', 9 | value: _self.obj.value 10 | }, 11 | on: { 12 | "on-change" (arr) { 13 | if (!_self.obj.name) { 14 | return false; 15 | } 16 | _self.obj.value = arr; 17 | _self.$emit('handleChangeVal', arr) 18 | } 19 | } 20 | }) 21 | ] 22 | } 23 | 24 | 25 | export let datePickerConf = { 26 | // 对应数据库内类型 27 | type: 'datepicker', 28 | // 是否可配置 29 | config: true, 30 | // 控件左侧label内容 31 | label: '时间选择', 32 | placeholder: '请选择日期', 33 | // 是否显示行内元素 34 | inlineBlock: false, 35 | // 是否必填 36 | require: true, 37 | // 表单name 38 | name: '', 39 | // 绑定的值 40 | value: "", 41 | // 验证错误提示信息 42 | ruleError: '选项不能为空', 43 | // 是否关联字段 44 | relation: false, 45 | // 关联字段name 46 | relation_name: '', 47 | // 关联字段value 48 | relation_value: '', 49 | // 是否被渲染 50 | visibility: true, 51 | // 是否需要时分 52 | format: 'yyyy年MM月dd日' 53 | } 54 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Select.js: -------------------------------------------------------------------------------- 1 | export default (_self, h) => { 2 | return [ 3 | h( 4 | "Select", { 5 | props: { 6 | placeholder: _self.obj.placeholder || "这是一个下拉选项框", 7 | value: _self.obj.value || '' 8 | }, 9 | on: { 10 | 'on-change' (value) { 11 | if (!_self.obj.name) { 12 | return false; 13 | } 14 | _self.obj.value = value; 15 | _self.$emit('handleChangeVal', value) 16 | } 17 | } 18 | }, 19 | _self.obj.items.map(v => { 20 | return h( 21 | "Option", { 22 | props: { 23 | value: "" + v.label_value 24 | }, 25 | }, 26 | v.label_name 27 | ); 28 | }) 29 | ) 30 | ]; 31 | }; 32 | 33 | export let selectConf = { 34 | // 对应数据库内类型 35 | type: 'select', 36 | // 是否可配置 37 | config: true, 38 | // 控件左侧label内容 39 | label: '选择框', 40 | placeholder: '', 41 | // 是否显示行内元素 42 | inlineBlock: false, 43 | // 是否必填 44 | require: true, 45 | // 选项内数据 46 | items: Array.apply(null, { length: 5 }) 47 | .map((k, v) => { 48 | return { 49 | label_value: v + 1, 50 | label_name: "选项" + (v + 1), 51 | } 52 | }), 53 | // 绑定的值 54 | value: '', 55 | // 表单name 56 | name: '', 57 | // 验证错误提示信息 58 | ruleError: '请选择', 59 | // 是否关联字段 60 | relation: false, 61 | // 关联字段name 62 | relation_name: '', 63 | // 关联字段value 64 | relation_value: '', 65 | // 是否被渲染 66 | visibility: true 67 | } 68 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Cascader.js: -------------------------------------------------------------------------------- 1 | const temp_data = (size, parentIndex, parentName) => { 2 | if (parentIndex >= 3) { 3 | return []; 4 | } 5 | return Array.apply(null, { length: size }) 6 | .map((k, v) => { 7 | const name = parentName + (v + 1) + "-"; 8 | return { 9 | value: name.substring(0, name.length - 1), 10 | label: name.substring(0, name.length - 1), 11 | children: temp_data(size, parentIndex + 1, name) 12 | } 13 | }); 14 | } 15 | 16 | export default (_self, h) => { 17 | return [ 18 | h("cascaderMulti", { 19 | props: { 20 | placeholder: _self.obj.placeholder || "这是一个级联选择器", 21 | data: temp_data(3, 0, '数据'), 22 | multiple: _self.obj.multiple || false, 23 | separate: _self.obj.multiple ? "," : "/", 24 | filterable: true, 25 | value: _self.obj.value || [] 26 | }, 27 | on: { 28 | "on-change" (arr) { 29 | if (!_self.obj.name) { 30 | return false; 31 | } 32 | _self.obj.value = arr; 33 | _self.$emit('handleChangeVal', arr) 34 | } 35 | } 36 | }) 37 | ]; 38 | }; 39 | 40 | export let cascaderConf = { 41 | // 对应数据库内类型 42 | type: 'cascader', 43 | // 是否可配置 44 | config: true, 45 | // 控件左侧label内容 46 | label: '级联选择', 47 | placeholder: '', 48 | // 是否显示行内元素 49 | inlineBlock: false, 50 | // 是否必填 51 | require: true, 52 | // 是否多选 53 | multiple: false, 54 | // 表单name 55 | name: '', 56 | // 绑定的值 57 | value: [], 58 | // 验证错误提示信息 59 | ruleError: '该选项不能为空', 60 | // 是否关联字段 61 | relation: false, 62 | // 关联字段name 63 | relation_name: '', 64 | // 关联字段value 65 | relation_value: '', 66 | // 是否被渲染 67 | visibility: true 68 | } 69 | -------------------------------------------------------------------------------- /src/components/render.vue: -------------------------------------------------------------------------------- 1 | 12 | 51 | 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-formbuilder", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "mrabit ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "cascader-multi": "^2.0.1", 15 | "iview": "^2.12.0", 16 | "iview-area": "^1.5.17", 17 | "moment": "^2.22.0", 18 | "vue": "^2.5.2", 19 | "vue-router": "^3.0.1", 20 | "vuedraggable": "^2.16.0" 21 | }, 22 | "devDependencies": { 23 | "ajv": "^6.4.0", 24 | "autoprefixer": "^7.1.2", 25 | "babel-core": "^6.22.1", 26 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 27 | "babel-loader": "^7.1.1", 28 | "babel-plugin-syntax-jsx": "^6.18.0", 29 | "babel-plugin-transform-runtime": "^6.22.0", 30 | "babel-plugin-transform-vue-jsx": "^3.5.0", 31 | "babel-preset-env": "^1.3.2", 32 | "babel-preset-stage-2": "^6.22.0", 33 | "chalk": "^2.0.1", 34 | "copy-webpack-plugin": "^4.0.1", 35 | "css-loader": "^0.28.0", 36 | "extract-text-webpack-plugin": "^3.0.0", 37 | "file-loader": "^1.1.4", 38 | "friendly-errors-webpack-plugin": "^1.6.1", 39 | "html-webpack-plugin": "^2.30.1", 40 | "iview-loader": "^1.1.0", 41 | "node-notifier": "^5.1.2", 42 | "optimize-css-assets-webpack-plugin": "^3.2.0", 43 | "ora": "^1.2.0", 44 | "portfinder": "^1.0.13", 45 | "postcss-import": "^11.0.0", 46 | "postcss-loader": "^2.0.8", 47 | "postcss-url": "^7.2.1", 48 | "rimraf": "^2.6.0", 49 | "semver": "^5.3.0", 50 | "shelljs": "^0.7.6", 51 | "uglifyjs-webpack-plugin": "^1.1.1", 52 | "url-loader": "^0.5.8", 53 | "vue-loader": "^13.3.0", 54 | "vue-style-loader": "^3.0.1", 55 | "vue-template-compiler": "^2.5.2", 56 | "webpack": "^3.6.0", 57 | "webpack-bundle-analyzer": "^2.9.0", 58 | "webpack-dev-server": "^2.9.1", 59 | "webpack-merge": "^4.1.0" 60 | }, 61 | "engines": { 62 | "node": ">= 6.0.0", 63 | "npm": ">= 3.0.0" 64 | }, 65 | "browserslist": [ 66 | "> 1%", 67 | "last 2 versions", 68 | "not ie <= 8" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | var proxyConfig = require('./proxyConfig') 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | // proxyTable: {}, 14 | proxyTable: proxyConfig.proxy, 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | 24 | /** 25 | * Source Maps 26 | */ 27 | 28 | // https://webpack.js.org/configuration/devtool/#development 29 | devtool: 'cheap-module-eval-source-map', 30 | 31 | // If you have problems debugging vue-files in devtools, 32 | // set this to false - it *may* help 33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 | cacheBusting: true, 35 | 36 | cssSourceMap: true 37 | }, 38 | 39 | build: { 40 | // Template for index.html 41 | index: path.resolve(__dirname, '../dist/index.html'), 42 | 43 | // Paths 44 | assetsRoot: path.resolve(__dirname, '../dist'), 45 | assetsSubDirectory: 'static', 46 | assetsPublicPath: '/', 47 | 48 | /** 49 | * Source Maps 50 | */ 51 | 52 | productionSourceMap: true, 53 | // https://webpack.js.org/configuration/devtool/#production 54 | devtool: '#source-map', 55 | 56 | // Gzip off by default as many popular static hosts such as 57 | // Surge or Netlify already gzip all static assets for you. 58 | // Before setting to `true`, make sure to: 59 | // npm install --save-dev compression-webpack-plugin 60 | productionGzip: false, 61 | productionGzipExtensions: ['js', 'css'], 62 | 63 | // Run the build command with an extra argument to 64 | // View the bundle analyzer report after build finishes: 65 | // `npm run build --report` 66 | // Set to `true` or `false` to always turn it on or off 67 | bundleAnalyzerReport: process.env.npm_config_report 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/custom_form/control/Address.js: -------------------------------------------------------------------------------- 1 | import area from '../config/area' 2 | export default (_self, h) => { 3 | let control = [ 4 | h('Cascader', { 5 | class: { 6 | 'ivu-input-wrapper ': !_self.obj.details_address 7 | }, 8 | style: { 9 | width: _self.obj.details_address ? '200px' : '100%', 10 | display: 'inline-block' 11 | }, 12 | props: { 13 | placeholder: _self.obj.placeholder || (_self.obj.name ? "" : "请选择详细地址"), 14 | data: area, 15 | value: _self.obj.value || [], 16 | filterable: false, 17 | 'change-on-select': true, 18 | // trigger: "hover" 19 | }, 20 | on: { 21 | "on-change" (arr) { 22 | if (!_self.obj.name) { 23 | return false; 24 | } 25 | _self.obj.value = arr; 26 | _self.$emit('handleChangeVal', arr); 27 | } 28 | } 29 | }), 30 | h("Input", { 31 | props: { 32 | placeholder: _self.obj.placeholder || "请输入详细地址", 33 | ref: 'details_address', 34 | value: (_self.obj.value[3] || {}) 35 | .name 36 | }, 37 | style: { 38 | width: 'auto', 39 | display: !_self.obj.name ? 'none' : 'inline-block', 40 | 'margin-left': '5px', 41 | 'min-width': '300px' 42 | }, 43 | on: { 44 | "on-change": function(val) { 45 | if (!_self.obj.name) { 46 | return false; 47 | } 48 | let temp_data = _self.obj.value.slice(0, 3); 49 | _self.obj.value = temp_data.concat(val.currentTarget.value); 50 | _self.$emit('handleChangeVal', _self.obj.value) 51 | } 52 | } 53 | }) 54 | ]; 55 | 56 | if (!_self.obj.details_address) { 57 | control.splice(1, 1); 58 | } 59 | return control; 60 | } 61 | export let addressConf = { 62 | // 对应数据库内类型 63 | type: 'address', 64 | // 是否可配置 65 | config: true, 66 | // 控件左侧label内容 67 | label: '详细地址', 68 | placeholder: '请输入详细地址', 69 | // 是否显示行内元素 70 | inlineBlock: false, 71 | // 是否必填 72 | require: true, 73 | // 是否多选 74 | multiple: false, 75 | // 表单name 76 | name: '', 77 | // 绑定的值 78 | value: [], 79 | // 验证错误提示信息 80 | ruleError: '请选择并输入详细地址', 81 | // 是否关联字段 82 | relation: false, 83 | // 关联字段name 84 | relation_name: '', 85 | // 关联字段value 86 | relation_value: '', 87 | // 是否被渲染 88 | visibility: true, 89 | // 是否需要详细地址 90 | details_address: true 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于Vue + Vue.Draggable实现自定义表单控件 2 | 3 | > 新项目需要用到工作流设定 + 自定义表单控件,这里列出了自定义表单控件的代码实现,可实现自定义表单控件,可拖拽排序,自定义属性 4 | 5 | > 项目 UI 组件库为 [iView](https://www.iviewui.com/docs/guide/install), [Element UI](http://element-cn.eleme.io/#/zh-CN) 可根据项目内代码进行适当修改达到适用 6 | 7 | > Element版本Demo: [https://yunpan.360.cn/surl_ymAY4CPrMNx](https://yunpan.360.cn/surl_ymAY4CPrMNx) (提取码:0e00) 8 | 9 | ## 效果预览 10 | 11 | ![image](https://ws2.sinaimg.cn/large/8fa21aabgy1ft4umsceqkg212n0ae4ik.gif) 12 | 13 | ![image](https://ws2.sinaimg.cn/large/8fa21aabgy1ft4umzqiofg212g0adak8.gif) 14 | 15 | ![image](https://wx1.sinaimg.cn/large/8fa21aabgy1ft4un9q3qmg212j0ei1kx.gif) 16 | 17 | ## 运行使用 18 | 19 | ``` bash 20 | # install dependencies 21 | npm install 22 | 23 | # serve with hot reload at localhost:8080 24 | npm run dev 25 | 26 | ``` 27 | 28 | ### 文件目录 29 | 30 | ``` 31 | . 32 | ├── README.md 33 | ├── build 34 | ├── config 35 | ├── dist 36 | ├── index.html 37 | ├── package.json 38 | ├── src 39 | │ ├── App.vue 40 | │ ├── assets 41 | │ ├── components 42 | │ │ ├── custom_form //自定义表单组件 43 | │ │ │ ├── FormList.js //表单列表 44 | │ │ │ ├── ItemIcon.js //表单图标配置 45 | │ │ │ ├── Render.js //表单列表渲染 46 | │ │ │ ├── components //表单公用组件 47 | │ │ │ │ └── Uploads //上传组件 48 | │ │ │ │ └── upload.vue 49 | │ │ │ ├── config //配置文件 50 | │ │ │ │ ├── area.js //地区配置 51 | │ │ │ │ └── trigger.js //表单验证触发事件 52 | │ │ │ ├── control //表单控件列表 53 | │   │   │   ├── Address.js //地区选择 54 | │   │   │   ├── Cascader.js //多级联动 55 | │   │   │   ├── CheckBox.js //多选框 56 | │   │   │   ├── DatePicker.js //时间选择器 57 | │   │   │   ├── Hr.js //hr标签 58 | │   │   │   ├── Input.js //输入框 59 | │   │   │   ├── P.js //p标签 60 | │   │   │   ├── Radio.js //单选框 61 | │   │   │   ├── Select.js //下拉选择框 62 | │   │   │   ├── Text.js //文本域 63 | │   │   │   ├── Title.js //标题 64 | │   │   │   └── Uploads.js //上传控件 65 | │ │ │ └── index.js //控件注册 66 | │   │   ├── index.vue //自定义表单页面 67 | │   │   └── render.vue //表单渲染,数据回填页面 68 | │ ├── main.js //入口文件 69 | │ └── router //路由配置 70 | │ └── index.js 71 | └── static //静态数据模版 72 | ├── label.1.json 73 | ├── label.12.json 74 | ├── label.14.json 75 | ├── label.17.json 76 | ├── label.19.json 77 | ├── label.3.json 78 | ├── label.5.json 79 | ├── label.8.json 80 | └── label.json 81 | ``` 82 | 83 | 相关插件: 84 | - [Vue.Draggable](https://github.com/SortableJS/Vue.Draggable) 85 | - [Vue.js](https://vuejs.org/) 86 | - [iView](https://www.iviewui.com/docs/guide/install) 87 | -------------------------------------------------------------------------------- /src/components/custom_form/Preview.js: -------------------------------------------------------------------------------- 1 | import title from './control/Title'; 2 | import hr from './control/Hr'; 3 | import p from './control/P'; 4 | 5 | import area from './config/area' 6 | 7 | const form_item = { 8 | title, 9 | hr, 10 | p 11 | }; 12 | 13 | // 获取地区控件值 14 | function getAddressValue(address, area, code = 0, str = "") { 15 | // 最后一步 16 | if (code == address.length - 1 && code == 3) return str + address[code]; 17 | for (let i in area) { 18 | if (area[i].value == address[code]) { 19 | str = str + getAddressValue(address, area[i].children, code + 1, area[i].label); 20 | break; 21 | } 22 | } 23 | return str; 24 | } 25 | 26 | function getCascaderValue(address, area, code = 0, str = "") { 27 | // 最后一步 28 | for (let i in area) { 29 | if (area[i].value == address[code]) { 30 | str = (str ? str + " > " : "") + getCascaderValue(address, area[i].children, code + 1, area[i].label); 31 | break; 32 | } 33 | } 34 | return str; 35 | } 36 | 37 | function getSelectValue(value, items) { 38 | return items.filter(v => { 39 | return v.label_value == value; 40 | }) 41 | } 42 | 43 | function getCheckBoxValue(value, items) { 44 | return items.filter(v => { 45 | return value.indexOf(v.label_value) >= 0; 46 | }) 47 | } 48 | 49 | // 获取控件值 50 | const getObjValue = (ele, obj) => { 51 | // 当控件为'input', 'text', 'datepicker',返回本身value 52 | const arr = [ 53 | 'input', 'text', 'datepicker' 54 | ] 55 | if (arr.indexOf(ele) >= 0 || !obj.value) { 56 | return obj.value; 57 | } 58 | 59 | if (ele === "address") { 60 | return getAddressValue(obj.value, area); 61 | } 62 | 63 | if (ele === "cascader") { 64 | return getCascaderValue(obj.value, obj.items); 65 | } 66 | 67 | const items = obj.items; 68 | const value = obj.value; 69 | if (ele == "select" || ele == "radio") { 70 | return (getSelectValue(value, items)[0] || {}) 71 | .label_name;; 72 | } 73 | 74 | if (ele == "checkbox") { 75 | let str = ""; 76 | getCheckBoxValue(value, items) 77 | .forEach(v => { 78 | str += (v.label_name + ","); 79 | }) 80 | return str.substring(0, str.length - 1); 81 | } 82 | 83 | return (items[value] || {}) 84 | .label_name || ""; 85 | } 86 | 87 | export default { 88 | name: 'preview', 89 | render(h) { 90 | // 关联的组件判断是否展示 91 | if (!this.obj.visibility) { 92 | return h('span'); 93 | } 94 | // 非 Title Hr P 95 | if (['title', 'hr', 'p'].indexOf(this.ele.toLowerCase()) < 0) { 96 | let control; 97 | if (this.ele.toLowerCase() != "uploads") { 98 | control = h('span', getObjValue(this.ele.toLowerCase(), this.obj)); 99 | } else { 100 | control = h('ul', { class: { 'pull-left': true }, style: { 'list-style': 'none' } }, 101 | this.obj.value.map(v => { 102 | return h('li', [h('a', { 103 | attrs: { 104 | target: '_blank', 105 | href: v.url 106 | } 107 | }, v.file_name)]); 108 | }) 109 | ) 110 | } 111 | return h('div', { 112 | class: { 113 | 'clearfix': true 114 | }, 115 | style: { 116 | // 是否显示行内元素 117 | display: this.obj.inlineBlock ? 'inline-block' : 'block', 118 | // 行内元素width为30% 119 | width: this.obj.inlineBlock ? '33%' : 'auto', 120 | 'margin-bottom': '24px' 121 | } 122 | }, [ 123 | h('label', { class: { 'text-right': true, 'pull-left': this.ele.toLowerCase() == "uploads" } }, this.obj.label + ":"), 124 | control, 125 | ]) 126 | } else { 127 | // 获取当前控件渲染 128 | const arr = (form_item[this.ele.toLowerCase()] && form_item[this.ele.toLowerCase()](this, h)) || []; 129 | return arr[0]; 130 | } 131 | }, 132 | props: { 133 | // 当前控件的类型 134 | ele: { 135 | type: String, 136 | default: "input" 137 | }, 138 | // 当前控件的配置 139 | obj: { 140 | type: Object, 141 | default () { 142 | return {}; 143 | } 144 | }, 145 | labelWidth: { 146 | type: Number, 147 | default: 100 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/components/custom_form/Render.js: -------------------------------------------------------------------------------- 1 | import ItemIcon from './ItemIcon'; 2 | import input from './control/Input'; 3 | import checkbox from './control/CheckBox'; 4 | import radio from './control/Radio'; 5 | import select from './control/Select'; 6 | import text from './control/Text'; 7 | import cascader from './control/Cascader'; 8 | import title from './control/Title'; 9 | import hr from './control/Hr'; 10 | import p from './control/P'; 11 | import uploads from './control/Uploads'; 12 | import datepicker from './control/DatePicker'; 13 | import address from './control/Address'; 14 | 15 | import trigger from './config/trigger'; 16 | 17 | const form_item = { 18 | title, 19 | hr, 20 | p, 21 | input, 22 | select, 23 | radio, 24 | checkbox, 25 | datepicker, 26 | cascader, 27 | address, 28 | uploads, 29 | text, 30 | }; 31 | 32 | const displayControl = (_self, sortableItem, name, value) => { 33 | // 默认不显示 34 | let display = false; 35 | for (let i in sortableItem) { 36 | // 循环出sortableItem内被关联字段并且其状态为显示并且其值与用户预设被关联字段值匹配 37 | // 不匹配,进行下一次判断 38 | if (sortableItem[i].obj.name != name || !sortableItem[i].obj.visibility) { 39 | continue; 40 | } 41 | // checkbox的value为数组, 判断是否存在 非数组直接比对字符串 42 | if ((Array.isArray(sortableItem[i].obj.value) && sortableItem[i].obj.value.indexOf(value) >= 0) || 43 | sortableItem[i].obj.value == value) { 44 | display = true; 45 | // name唯一,已匹配则不必循环之后数据 46 | break; 47 | } 48 | } 49 | return display; 50 | } 51 | 52 | export default { 53 | name: 'renders', 54 | render(h) { 55 | var $this = this; 56 | // 获取当前控件渲染 57 | const arr = (form_item[this.ele.toLowerCase()] && form_item[this.ele.toLowerCase()](this, h)) || []; 58 | // 拥有绑定的值,需回填到控件 59 | this.$set(this.obj, 'value', typeof this.value !== "undefined" ? this.value : this.obj.value); 60 | // 显示配置按钮并且控件允许被配置 61 | const item_icon = this.configIcon && this.obj.config ? ItemIcon(this, h) : []; 62 | // 已被绑定name,且require为必填,视为校验字段 63 | const validate = !!this.obj.name && !!this.obj.require; 64 | // 非 Title Hr P 需要FormItem 65 | if (['title', 'hr', 'p'].indexOf((this.ele.toLowerCase())) < 0) { 66 | // 关联的组件判断是否展示 67 | if (this.obj.relation && !displayControl(this, this.sortableItem, this.obj.relation_name, this.obj.relation_value)) { 68 | // 隐藏该控件并设置该控件标记为隐藏 69 | this.$emit('changeVisibility', this.index, false); 70 | return h("span"); 71 | } 72 | // 设置该控件标记为显示 73 | this.$emit('changeVisibility', this.index, true); 74 | let FormItem = { 75 | class: { 76 | 'items': true, 77 | 'sortable-items-required': validate 78 | }, 79 | props: { 80 | label: (this.obj.label || this.ele) + ':', 81 | // 指定验证name 82 | prop: this.obj.name || 'temp', 83 | // 验证规则 84 | rules: { 85 | required: validate, 86 | message: this.obj.ruleError || '该项为必填项', 87 | trigger: trigger[this.obj.type], 88 | validator: (rule, value, callback) => { 89 | // 没有配置按钮并且允许验证 90 | if (!this.configIcon && validate && (Array.isArray(value) ? !value.length : !value)) { 91 | callback(new Error('该项为必填项')); 92 | } else { 93 | callback(); 94 | } 95 | } 96 | }, 97 | }, 98 | style: { 99 | // 是否显示行内元素 100 | display: this.obj.inlineBlock ? 'inline-block' : 'block', 101 | // 行内元素width为30% 102 | width: this.obj.inlineBlock ? '33%' : 'auto', 103 | } 104 | }; 105 | return h( 106 | "FormItem", FormItem, 107 | arr.concat(item_icon) 108 | ); 109 | } else { 110 | return h( 111 | "div", { 112 | style: { 113 | 'position': 'relative' 114 | }, 115 | class: { 116 | items: true 117 | }, 118 | }, 119 | arr.concat(item_icon) 120 | ); 121 | } 122 | }, 123 | props: { 124 | // 当前控件的类型 125 | ele: { 126 | type: String, 127 | default: "input" 128 | }, 129 | // 当前控件的配置 130 | obj: { 131 | type: Object, 132 | default () { 133 | return {}; 134 | } 135 | }, 136 | // 当前控件的index,config 和 delete 会用到 137 | index: { 138 | type: Number, 139 | default: 0 140 | }, 141 | // 整个表单的数据 142 | data: { 143 | type: Object, 144 | default () { 145 | return {} 146 | } 147 | }, 148 | // 是否显示配置按钮 149 | configIcon: { 150 | type: Boolean, 151 | default: false 152 | }, 153 | // 当前控件绑定的值 方便数据回填 154 | value: [String, Number, Array], 155 | // 当前被clone控件列表 156 | sortableItem: { 157 | type: Array, 158 | default () { 159 | return []; 160 | } 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/components/custom_form/components/Uploads/upload.vue: -------------------------------------------------------------------------------- 1 | 37 | 142 | 178 | -------------------------------------------------------------------------------- /src/components/index.vue: -------------------------------------------------------------------------------- 1 | 114 | 306 | --------------------------------------------------------------------------------