Commit 5bbd40ed2fc288108fa039dffa3a6d3c35236982
1 parent
2db4f03a
Exists in
master
and in
1 other branch
新式表单支持两种布局模式
Showing
4 changed files
with
121 additions
and
30 deletions
Show diff stats
examples/views/page/form-new/form-render.vue
| 1 | 1 | <template> |
| 2 | 2 | <!-- 在row上使用flex,防止表单组件大小不一导致错位 --> |
| 3 | - <el-row type="flex" style="flex-wrap: wrap;width: 100%;" :class="contentClass || 'eagle-form__group-content'"> | |
| 3 | + <component :is="rowComponent" class="eagle-form__flex-wrap" :class="contentClass || 'eagle-form__group-content'"> | |
| 4 | 4 | <template v-for="(item, index) in list"> |
| 5 | 5 | <!-- 表单项有设置分组时 --> |
| 6 | - <el-col v-if="item.group && item.list" :key="index" :span="item.group.span || span"> | |
| 7 | - <el-row type="flex" style="flex-wrap: wrap;width: 100%;"> | |
| 6 | + <component :is="colComponent" v-if="item.group && item.list" :key="index" | |
| 7 | + :span="type === 'div' ? undefined : item.group.span || 24" :style="{ width: type === 'div' ? '100%' : undefined }" | |
| 8 | + > | |
| 9 | + <component :is="rowComponent" class="eagle-form__flex-wrap"> | |
| 8 | 10 | <!-- 表单分组标题 --> |
| 9 | - <el-col :class="titleClass || 'eagle-form__group-title'" v-if="item.group.title" :span="24">{{ item.group.title || item.group }}</el-col> | |
| 11 | + <component :is="rowComponent" :class="titleClass || 'eagle-form__group-title'" v-if="item.group.title" style="width: 100%;"> | |
| 12 | + {{ item.group.title || item.group }} | |
| 13 | + </component> | |
| 10 | 14 | <!-- 递归本组件 --> |
| 11 | - <form-render :title-class="titleClass" :list="item.list" :value="value" :model="itemKey ? model[itemKey] || {} : model" @item-change="onItemChange" @form-item-change="onFormItemChange" :itemKey="item.group.key" @item-update="onItemUpdate"></form-render> | |
| 12 | - </el-row> | |
| 13 | - </el-col> | |
| 15 | + <form-render :title-class="titleClass" :item-class="itemClass" :list="item.list" :value="value" | |
| 16 | + :model="itemKey ? model[itemKey] || {} : model" :itemKey="item.group.key" :type="type" | |
| 17 | + @item-change="onItemChange" @form-item-change="onFormItemChange" @item-update="onItemUpdate" | |
| 18 | + :span="type === 'div' ? undefined : span * (24 / (item.group.span || 24))" | |
| 19 | + ></form-render> | |
| 20 | + </component> | |
| 21 | + </component> | |
| 14 | 22 | <!-- 正常无分组表单项 --> |
| 15 | - <el-col v-else :span="item.span || span" :key="index"> | |
| 16 | - <el-form-item :label="item.label" :label-width="item.labelWidth || '120px'" :prop="item.fullKey" :rules="item.rules"> | |
| 23 | + <component :is="colComponent" v-else :span="type === 'div' ? undefined : item.span || span" :key="index" | |
| 24 | + :style="{ width: type === 'div' && item.style && item.style.width.indexOf('%') > -1 ? item.style.width : undefined, paddingRight: '10px' }" | |
| 25 | + > | |
| 26 | + <el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.fullKey" :rules="item.rules" :class="itemClass || 'eagle-form__item'"> | |
| 17 | 27 | <!-- 自定义组件 --> |
| 18 | - <component :is="item.type" :value="itemValue(item)" @input="v => onInput({ value: v, item })" v-on="bindItemEvent(item)"></component> | |
| 28 | + <component :is="item.type" :value="itemValue(item)" @input="v => onInput({ value: v, item })" | |
| 29 | + v-on="bindItemEvent(item)" v-bind="item.props" :style="item.style || { maxWidth: '100%' }" | |
| 30 | + ></component> | |
| 19 | 31 | </el-form-item> |
| 20 | - </el-col> | |
| 32 | + </component> | |
| 21 | 33 | </template> |
| 22 | - </el-row> | |
| 34 | + </component> | |
| 23 | 35 | </template> |
| 24 | 36 | |
| 25 | 37 | <script> |
| ... | ... | @@ -32,8 +44,18 @@ export default { |
| 32 | 44 | itemKey: String, |
| 33 | 45 | titleClass: String, |
| 34 | 46 | contentClass: String, |
| 47 | + itemClass: String, | |
| 48 | + type: String, | |
| 35 | 49 | span: Number, |
| 36 | 50 | }, |
| 51 | + computed: { | |
| 52 | + rowComponent() { | |
| 53 | + return this.type === 'div' ? 'div' : 'el-row'; | |
| 54 | + }, | |
| 55 | + colComponent() { | |
| 56 | + return this.type === 'div' ? 'div' : 'el-col'; | |
| 57 | + }, | |
| 58 | + }, | |
| 37 | 59 | methods: { |
| 38 | 60 | /** |
| 39 | 61 | * @description 根据表单项的key查询该值 | ... | ... |
examples/views/page/form-new/index.vue
| 1 | +<style> | |
| 2 | +.el-input .el-input__inner, .el-textarea__inner { | |
| 3 | + border-radius: 0; | |
| 4 | +} | |
| 5 | +</style> | |
| 6 | + | |
| 7 | +<style> | |
| 8 | +.eagle-form__flex-wrap { | |
| 9 | + display: flex; | |
| 10 | + flex-wrap: wrap; | |
| 11 | + width: 100%; | |
| 12 | +} | |
| 13 | +.eagle-form__group-title { | |
| 14 | + font-weight: bold; | |
| 15 | + padding: 15px 5px; | |
| 16 | + border-bottom: 1px solid #d9d9d9; | |
| 17 | + margin-bottom: 30px; | |
| 18 | +} | |
| 19 | +.eagle-form__group-content { | |
| 20 | + margin: 15px 0px; | |
| 21 | +} | |
| 22 | +</style> | |
| 23 | + | |
| 1 | 24 | <template> |
| 2 | - <el-form ref="form" size="small" :class="formClass" :model="formModel"> | |
| 25 | + <el-form ref="form" size="mini" :class="formClass" :model="formModel" :label-width="labelWidth" :label-position="labelPosition || labelWidth ? 'right' : 'top'"> | |
| 3 | 26 | {{ formModel }} |
| 4 | - <form-render :title-class="titleClass" :content-class="contentClass" :list="formList" :value="model" :model="model" :span="span" | |
| 5 | - @item-change="onItemChange" @form-item-change="onFormItemChange" @item-update="onItemUpdate"></form-render> | |
| 27 | + <form-render :title-class="titleClass" :content-class="contentClass" :item-class="itemClass" :list="formList" :value="model" | |
| 28 | + :model="model" :span="span" :type="type" | |
| 29 | + @item-change="onItemChange" @form-item-change="onFormItemChange" @item-update="onItemUpdate" | |
| 30 | + ></form-render> | |
| 6 | 31 | {{ list }} |
| 7 | 32 | </el-form> |
| 8 | 33 | </template> |
| ... | ... | @@ -20,6 +45,10 @@ export default { |
| 20 | 45 | formClass: String, |
| 21 | 46 | titleClass: String, |
| 22 | 47 | contentClass: String, |
| 48 | + itemClass: String, | |
| 49 | + labelWidth: String, | |
| 50 | + labelPosition: String, | |
| 51 | + type: String, | |
| 23 | 52 | span: { |
| 24 | 53 | type: Number, |
| 25 | 54 | default: 24 |
| ... | ... | @@ -85,7 +114,7 @@ export default { |
| 85 | 114 | */ |
| 86 | 115 | validate() { |
| 87 | 116 | this.$refs.form.validate(valid => { |
| 88 | - this.$emit("validate", valid); | |
| 117 | + this.$emit("validate", valid, this.model); | |
| 89 | 118 | }); |
| 90 | 119 | }, |
| 91 | 120 | /** |
| ... | ... | @@ -112,6 +141,7 @@ export default { |
| 112 | 141 | * @description 手动更新某一表单项的值 |
| 113 | 142 | * @param {Object} param 需要更新的参数对象或者对象数组 { name => 表单项key,可嵌套; value => 更新的值 } |
| 114 | 143 | * @example { name: 'a.b.c', value: 123 } |
| 144 | + * @example { name: 'd.0.e', value: ['f'] } | |
| 115 | 145 | */ |
| 116 | 146 | onItemUpdate(param) { |
| 117 | 147 | this.$nextTick(() => { | ... | ... |
examples/views/page/form-new/util.js
| 1 | +/** | |
| 2 | + * 深度克隆对象 | |
| 3 | + * @param {Object} obj 目标对象 | |
| 4 | + * @returns {Object} 克隆的新对象 | |
| 5 | + */ | |
| 1 | 6 | export const cloneDeep = (obj) => { |
| 2 | 7 | if (typeof obj !== 'object') { |
| 3 | 8 | return obj; |
| ... | ... | @@ -35,19 +40,31 @@ export const cloneDeep = (obj) => { |
| 35 | 40 | return newObj; |
| 36 | 41 | }; |
| 37 | 42 | |
| 43 | +/** | |
| 44 | + * 对象深度取值 | |
| 45 | + * @desctiption 来源于"typy.js"中src/util的getNestedObject函数 | |
| 46 | + * @param {Object} obj 目标对象 | |
| 47 | + * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的取值路径 | |
| 48 | + * @example get({ a: { b: { c: ['d'] } } }, 'a.b.c.0') | |
| 49 | + */ | |
| 38 | 50 | export const get = (obj, dotSeparatedKeys) => { |
| 39 | 51 | if (dotSeparatedKeys !== undefined && typeof dotSeparatedKeys !== 'string') return undefined; |
| 40 | 52 | if (typeof obj !== 'undefined' && typeof dotSeparatedKeys === 'string') { |
| 41 | - // split on ".", "[", "]", "'", """ and filter out empty elements | |
| 42 | - const splitRegex = /[.\[\]'"]/g; // eslint-disable-line no-useless-escape | |
| 53 | + const splitRegex = /[.\[\]'"]/g; | |
| 43 | 54 | const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== ''); |
| 44 | - | |
| 45 | - // eslint-disable-next-line no-param-reassign, no-confusing-arrow | |
| 46 | 55 | obj = pathArr.reduce((o, key) => (o && o[key] !== undefined ? o[key] : undefined), obj); |
| 47 | 56 | } |
| 48 | 57 | return obj; |
| 49 | 58 | }; |
| 50 | 59 | |
| 60 | +/** | |
| 61 | + * 对象深度赋值 | |
| 62 | + * @description 改写自get方法 | |
| 63 | + * @param {Object} obj 目标对像 | |
| 64 | + * @param {String} dotSeparatedKeys 用分隔符".", "[", "]", "'", """隔开的赋值路径 | |
| 65 | + * @param {*} value 目标值 | |
| 66 | + * @example set(obj, 'a.b.c', 'd') | |
| 67 | + */ | |
| 51 | 68 | export const set = (obj, dotSeparatedKeys, value) => { |
| 52 | 69 | const splitRegex = /[.\[\]'"]/g; |
| 53 | 70 | const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== ''); |
| ... | ... | @@ -58,7 +75,6 @@ export const set = (obj, dotSeparatedKeys, value) => { |
| 58 | 75 | } |
| 59 | 76 | return o[k] |
| 60 | 77 | }, obj)[key] = value |
| 61 | - // pathArr.reduce((o, key) => (o && o[key] !== 'undefined' ? o[key] : undefined), obj)[key] = value | |
| 62 | 78 | }; |
| 63 | 79 | |
| 64 | 80 | export default { | ... | ... |
examples/views/page/other.vue
| ... | ... | @@ -15,7 +15,11 @@ |
| 15 | 15 | <p>这是一个非markdown页面</p> |
| 16 | 16 | <pre>{{ model }}</pre> |
| 17 | 17 | <el-button size="mini" @click="handleGetValue">校验</el-button> |
| 18 | - <eg-form ref="form" v-model="model" :list="option.list" @validate="onValidate" :span="12" form-class="custom-form" title-class="custom-title" content-class="custom-content"></eg-form> | |
| 18 | + <!-- <eg-form ref="form" v-model="model" :list="option.list" @validate="onValidate" label-width="80px" :span="6" type="div"></eg-form> --> | |
| 19 | + <eg-form | |
| 20 | + ref="form" v-model="model" :list="option.list" @validate="onValidate" :span="4" | |
| 21 | + form-class="custom-form" title-class="custom-title" content-class="custom-content" item-class="custom-item" | |
| 22 | + ></eg-form> | |
| 19 | 23 | </div> |
| 20 | 24 | </template> |
| 21 | 25 | |
| ... | ... | @@ -36,14 +40,14 @@ export default { |
| 36 | 40 | option: { |
| 37 | 41 | list: [ |
| 38 | 42 | { |
| 39 | - group: { title: '基础信息' }, | |
| 43 | + group: { title: '基础信息', span: 12 }, | |
| 40 | 44 | list: [ |
| 41 | 45 | { type: 'el-input', label: '名称', key: 'name' }, |
| 42 | 46 | { type: 'el-input-number', label: '年龄', key: 'age' }, |
| 43 | 47 | ], |
| 44 | 48 | }, |
| 45 | 49 | { |
| 46 | - group: { title: '' }, | |
| 50 | + group: { title: '', span: 12 }, | |
| 47 | 51 | }, |
| 48 | 52 | { |
| 49 | 53 | group: { title: '住址', key: 'location', span: 24 }, |
| ... | ... | @@ -52,19 +56,20 @@ export default { |
| 52 | 56 | { |
| 53 | 57 | group: { key: 'district' }, |
| 54 | 58 | list: [ |
| 55 | - { type: 'el-input', label: '省', key: 'province', rules: [{ required: true, message: '请输入省' }], span: 12 }, | |
| 56 | - { type: 'el-input', label: '市', key: 'city', span: 12 }, | |
| 59 | + { type: 'el-input', label: '省', key: 'province', rules: [{ required: true, message: '请输入省' }] }, | |
| 60 | + { type: 'el-input', label: '市', key: 'city' }, | |
| 57 | 61 | ], |
| 58 | 62 | }, |
| 59 | 63 | { |
| 60 | 64 | group: { title: '小区信息' }, |
| 61 | 65 | list: [ |
| 62 | - { type: 'el-input', label: '小区名', key: 'areaName', span: 12 }, | |
| 63 | - { type: 'el-input', label: '门牌号', key: 'homeNum', span: 12 }, | |
| 66 | + { type: 'el-input', label: '小区名', key: 'areaName' }, | |
| 67 | + { type: 'el-input', label: '门牌号', key: 'homeNum' }, | |
| 64 | 68 | { |
| 65 | 69 | group: { title: 'A栋' }, |
| 66 | 70 | list: [ |
| 67 | 71 | { type: 'el-input-number', label: '人数', key: 'anumber', |
| 72 | + props: { 'controls-position': 'right' }, | |
| 68 | 73 | on: { |
| 69 | 74 | change(value) { |
| 70 | 75 | console.log(value); |
| ... | ... | @@ -94,6 +99,24 @@ export default { |
| 94 | 99 | } |
| 95 | 100 | ], |
| 96 | 101 | }, |
| 102 | + { type: 'el-input', label: '身高', key: 'height', props: { type: 'textarea', min: 3 }, style: { width: '100%' }, span: 24 }, | |
| 103 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 104 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 105 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 106 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 107 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 108 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 109 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 110 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 111 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 112 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 113 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 114 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 115 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 116 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 117 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 118 | + { type: 'el-input', label: '身高', key: 'height' }, | |
| 119 | + { type: 'el-input', label: '体重', key: 'weight' }, | |
| 97 | 120 | { type: 'el-input', label: '身高', key: 'height' }, |
| 98 | 121 | { type: 'el-input', label: '体重', key: 'weight' }, |
| 99 | 122 | ] |
| ... | ... | @@ -124,8 +147,8 @@ export default { |
| 124 | 147 | }, 3000); |
| 125 | 148 | }, |
| 126 | 149 | methods: { |
| 127 | - onValidate(v) { | |
| 128 | - console.log(v); | |
| 150 | + onValidate(isValidated, model) { | |
| 151 | + console.log(isValidated, model); | |
| 129 | 152 | }, |
| 130 | 153 | handleGetValue() { |
| 131 | 154 | this.$refs.form.validate(); | ... | ... |