Commit 2db4f03a6f5111d97fa7f3304fbb635b0137f63c
1 parent
fd68ba0b
Exists in
master
and in
1 other branch
优化表单动态更新赋值
Showing
4 changed files
with
142 additions
and
51 deletions
Show diff stats
examples/views/page/form-new/form-render.vue
| 1 | <template> | 1 | <template> |
| 2 | <!-- 在row上使用flex,防止表单组件大小不一导致错位 --> | 2 | <!-- 在row上使用flex,防止表单组件大小不一导致错位 --> |
| 3 | - <el-row type="flex" style="flex-wrap: wrap;" :class="contentClass || 'eagle-form__group-content'"> | 3 | + <el-row type="flex" style="flex-wrap: wrap;width: 100%;" :class="contentClass || 'eagle-form__group-content'"> |
| 4 | <template v-for="(item, index) in list"> | 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 || 24"> | ||
| 7 | - <el-row type="flex" style="flex-wrap: wrap;"> | 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%;"> | ||
| 8 | <!-- 表单分组标题 --> | 8 | <!-- 表单分组标题 --> |
| 9 | <el-col :class="titleClass || 'eagle-form__group-title'" v-if="item.group.title" :span="24">{{ item.group.title || item.group }}</el-col> | 9 | <el-col :class="titleClass || 'eagle-form__group-title'" v-if="item.group.title" :span="24">{{ item.group.title || item.group }}</el-col> |
| 10 | <!-- 递归本组件 --> | 10 | <!-- 递归本组件 --> |
| @@ -12,7 +12,7 @@ | @@ -12,7 +12,7 @@ | ||
| 12 | </el-row> | 12 | </el-row> |
| 13 | </el-col> | 13 | </el-col> |
| 14 | <!-- 正常无分组表单项 --> | 14 | <!-- 正常无分组表单项 --> |
| 15 | - <el-col v-else :span="item.span || 12" :key="index"> | 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"> | 16 | <el-form-item :label="item.label" :label-width="item.labelWidth || '120px'" :prop="item.fullKey" :rules="item.rules"> |
| 17 | <!-- 自定义组件 --> | 17 | <!-- 自定义组件 --> |
| 18 | <component :is="item.type" :value="itemValue(item)" @input="v => onInput({ value: v, item })" v-on="bindItemEvent(item)"></component> | 18 | <component :is="item.type" :value="itemValue(item)" @input="v => onInput({ value: v, item })" v-on="bindItemEvent(item)"></component> |
| @@ -32,6 +32,7 @@ export default { | @@ -32,6 +32,7 @@ export default { | ||
| 32 | itemKey: String, | 32 | itemKey: String, |
| 33 | titleClass: String, | 33 | titleClass: String, |
| 34 | contentClass: String, | 34 | contentClass: String, |
| 35 | + span: Number, | ||
| 35 | }, | 36 | }, |
| 36 | methods: { | 37 | methods: { |
| 37 | /** | 38 | /** |
| @@ -92,7 +93,7 @@ export default { | @@ -92,7 +93,7 @@ export default { | ||
| 92 | bindItemEvent(item) { | 93 | bindItemEvent(item) { |
| 93 | if (item.on) { | 94 | if (item.on) { |
| 94 | if (typeof item.on === 'function') { | 95 | if (typeof item.on === 'function') { |
| 95 | - return item.on({ model: this.value, update: ({ name, value }) => this.$emit('item-update', { name, value }) }); | 96 | + return item.on({ model: this.value, update: e => this.$emit('item-update', e) }); |
| 96 | } else { | 97 | } else { |
| 97 | return item.on | 98 | return item.on |
| 98 | } | 99 | } |
examples/views/page/form-new/index.vue
| 1 | <template> | 1 | <template> |
| 2 | <el-form ref="form" size="small" :class="formClass" :model="formModel"> | 2 | <el-form ref="form" size="small" :class="formClass" :model="formModel"> |
| 3 | {{ formModel }} | 3 | {{ formModel }} |
| 4 | - <form-render :title-class="titleClass" :content-class="contentClass" :list="formList" :value="model" :model="model" @item-change="onItemChange" @form-item-change="onFormItemChange" @item-update="onItemUpdate"></form-render> | 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> | ||
| 5 | {{ list }} | 6 | {{ list }} |
| 6 | </el-form> | 7 | </el-form> |
| 7 | </template> | 8 | </template> |
| 8 | 9 | ||
| 9 | <script> | 10 | <script> |
| 10 | import FormRender from './form-render'; | 11 | import FormRender from './form-render'; |
| 11 | -import { cloneDeep, set } from 'lodash'; | 12 | +import { cloneDeep, set } from './util'; |
| 12 | 13 | ||
| 13 | export default { | 14 | export default { |
| 14 | name: 'FormNew', | 15 | name: 'FormNew', |
| @@ -19,40 +20,49 @@ export default { | @@ -19,40 +20,49 @@ export default { | ||
| 19 | formClass: String, | 20 | formClass: String, |
| 20 | titleClass: String, | 21 | titleClass: String, |
| 21 | contentClass: String, | 22 | contentClass: String, |
| 23 | + span: { | ||
| 24 | + type: Number, | ||
| 25 | + default: 24 | ||
| 26 | + } | ||
| 22 | }, | 27 | }, |
| 23 | data() { | 28 | data() { |
| 24 | return { | 29 | return { |
| 25 | model: {}, | 30 | model: {}, |
| 26 | formModel: {}, | 31 | formModel: {}, |
| 32 | + formList: [] | ||
| 27 | } | 33 | } |
| 28 | }, | 34 | }, |
| 29 | watch: { | 35 | watch: { |
| 30 | - value(val = {}) { | ||
| 31 | - this.model = val; | ||
| 32 | - this.setFormModel(val) | 36 | + value: { |
| 37 | + handler(val = {}) { | ||
| 38 | + this.model = val; | ||
| 39 | + this.setFormModel(val) | ||
| 40 | + }, | ||
| 41 | + immediate: true | ||
| 33 | }, | 42 | }, |
| 34 | - }, | ||
| 35 | - computed: { | ||
| 36 | - formList() { | ||
| 37 | - // 深度克隆传入的列表,避免原始值被修改 | ||
| 38 | - const newList = cloneDeep(this.list); | ||
| 39 | - // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 40 | - const generateFullKey = (list, parentKey) => { | ||
| 41 | - list.forEach(item => { | ||
| 42 | - if (item.group && item.list) { | ||
| 43 | - if (item.group.key) { | ||
| 44 | - item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`; | 43 | + list: { |
| 44 | + handler(val) { | ||
| 45 | + // 深度克隆传入的列表,避免原始值被修改 | ||
| 46 | + const newList = cloneDeep(this.list); | ||
| 47 | + // 生成列表值的全路径key,即列表项为对象时,对象内的key与上一级的key合并作为全路径key | ||
| 48 | + const generateFullKey = (list, parentKey) => { | ||
| 49 | + list.forEach(item => { | ||
| 50 | + if (item.group && item.list) { | ||
| 51 | + if (item.group.key) { | ||
| 52 | + item.fullKey = `${parentKey ? `${parentKey}-${item.group.key}` : item.group.key}`; | ||
| 53 | + } else { | ||
| 54 | + item.fullKey = item.key; | ||
| 55 | + } | ||
| 56 | + generateFullKey(item.list, item.fullKey); | ||
| 45 | } else { | 57 | } else { |
| 46 | - item.fullKey = item.key; | 58 | + item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`; |
| 47 | } | 59 | } |
| 48 | - generateFullKey(item.list, item.fullKey); | ||
| 49 | - } else { | ||
| 50 | - item.fullKey = `${parentKey ? `${parentKey}-${item.key}` : item.key}`; | ||
| 51 | - } | ||
| 52 | - }); | ||
| 53 | - }; | ||
| 54 | - generateFullKey(newList); | ||
| 55 | - return newList; | 60 | + }); |
| 61 | + }; | ||
| 62 | + generateFullKey(newList); | ||
| 63 | + this.formList = newList; | ||
| 64 | + }, | ||
| 65 | + immediate: true | ||
| 56 | } | 66 | } |
| 57 | }, | 67 | }, |
| 58 | methods: { | 68 | methods: { |
| @@ -100,13 +110,17 @@ export default { | @@ -100,13 +110,17 @@ export default { | ||
| 100 | }, | 110 | }, |
| 101 | /** | 111 | /** |
| 102 | * @description 手动更新某一表单项的值 | 112 | * @description 手动更新某一表单项的值 |
| 103 | - * @param {Object} { name => 表单项key,可嵌套; value => 更新的值 } | 113 | + * @param {Object} param 需要更新的参数对象或者对象数组 { name => 表单项key,可嵌套; value => 更新的值 } |
| 104 | * @example { name: 'a.b.c', value: 123 } | 114 | * @example { name: 'a.b.c', value: 123 } |
| 105 | */ | 115 | */ |
| 106 | - onItemUpdate({ name, value }) { | ||
| 107 | - const newModel = cloneDeep(this.model); | ||
| 108 | - set(newModel, name, value); | ||
| 109 | - this.$emit('input', newModel); | 116 | + onItemUpdate(param) { |
| 117 | + this.$nextTick(() => { | ||
| 118 | + const newModel = cloneDeep(this.model); | ||
| 119 | + Object.entries(param).forEach(entry => { | ||
| 120 | + set(newModel, entry[0], entry[1]); | ||
| 121 | + }); | ||
| 122 | + this.$emit('input', newModel); | ||
| 123 | + }); | ||
| 110 | } | 124 | } |
| 111 | } | 125 | } |
| 112 | } | 126 | } |
| @@ -0,0 +1,68 @@ | @@ -0,0 +1,68 @@ | ||
| 1 | +export const cloneDeep = (obj) => { | ||
| 2 | + if (typeof obj !== 'object') { | ||
| 3 | + return obj; | ||
| 4 | + } | ||
| 5 | + if (!obj) { | ||
| 6 | + return obj; | ||
| 7 | + } | ||
| 8 | + if (obj instanceof Date) { | ||
| 9 | + return new Date(obj); | ||
| 10 | + } | ||
| 11 | + if (obj instanceof RegExp) { | ||
| 12 | + return new RegExp(obj); | ||
| 13 | + } | ||
| 14 | + if (obj instanceof Function) { | ||
| 15 | + return obj; | ||
| 16 | + } | ||
| 17 | + let newObj; | ||
| 18 | + if (obj instanceof Array) { | ||
| 19 | + newObj = []; | ||
| 20 | + for(let i = 0, len = obj.length; i < len; i++){ | ||
| 21 | + newObj.push(cloneDeep(obj[i])); | ||
| 22 | + } | ||
| 23 | + return newObj; | ||
| 24 | + } | ||
| 25 | + newObj = {}; | ||
| 26 | + for(let key in obj) { | ||
| 27 | + if (obj.hasOwnProperty(key)) { | ||
| 28 | + if (typeof obj[key] !== 'object') { | ||
| 29 | + newObj[key] = obj[key]; | ||
| 30 | + } else { | ||
| 31 | + newObj[key] = cloneDeep(obj[key]); | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + return newObj; | ||
| 36 | +}; | ||
| 37 | + | ||
| 38 | +export const get = (obj, dotSeparatedKeys) => { | ||
| 39 | + if (dotSeparatedKeys !== undefined && typeof dotSeparatedKeys !== 'string') return undefined; | ||
| 40 | + 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 | ||
| 43 | + const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== ''); | ||
| 44 | + | ||
| 45 | + // eslint-disable-next-line no-param-reassign, no-confusing-arrow | ||
| 46 | + obj = pathArr.reduce((o, key) => (o && o[key] !== undefined ? o[key] : undefined), obj); | ||
| 47 | + } | ||
| 48 | + return obj; | ||
| 49 | +}; | ||
| 50 | + | ||
| 51 | +export const set = (obj, dotSeparatedKeys, value) => { | ||
| 52 | + const splitRegex = /[.\[\]'"]/g; | ||
| 53 | + const pathArr = dotSeparatedKeys.split(splitRegex).filter(k => k !== ''); | ||
| 54 | + const key = pathArr.pop(); | ||
| 55 | + pathArr.reduce((o, k) => { | ||
| 56 | + if (o && o[k] === undefined || o[k] === null) { | ||
| 57 | + o[k] = !isNaN(Number(key)) ? [] : {} | ||
| 58 | + } | ||
| 59 | + return o[k] | ||
| 60 | + }, obj)[key] = value | ||
| 61 | + // pathArr.reduce((o, key) => (o && o[key] !== 'undefined' ? o[key] : undefined), obj)[key] = value | ||
| 62 | +}; | ||
| 63 | + | ||
| 64 | +export default { | ||
| 65 | + cloneDeep, | ||
| 66 | + get, | ||
| 67 | + set | ||
| 68 | +} | ||
| 0 | \ No newline at end of file | 69 | \ No newline at end of file |
examples/views/page/other.vue
| @@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
| 15 | <p>这是一个非markdown页面</p> | 15 | <p>这是一个非markdown页面</p> |
| 16 | <pre>{{ model }}</pre> | 16 | <pre>{{ model }}</pre> |
| 17 | <el-button size="mini" @click="handleGetValue">校验</el-button> | 17 | <el-button size="mini" @click="handleGetValue">校验</el-button> |
| 18 | - <eg-form ref="form" v-model="model" :list="option.list" @validate="onValidate" 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" :span="12" form-class="custom-form" title-class="custom-title" content-class="custom-content"></eg-form> |
| 19 | </div> | 19 | </div> |
| 20 | </template> | 20 | </template> |
| 21 | 21 | ||
| @@ -27,36 +27,44 @@ export default { | @@ -27,36 +27,44 @@ export default { | ||
| 27 | components: { EgForm }, | 27 | components: { EgForm }, |
| 28 | data() { | 28 | data() { |
| 29 | return { | 29 | return { |
| 30 | - model: {}, | 30 | + model: { |
| 31 | + name: 'name', | ||
| 32 | + location: { | ||
| 33 | + areaName: 'hahhhahaa', | ||
| 34 | + }, | ||
| 35 | + }, | ||
| 31 | option: { | 36 | option: { |
| 32 | list: [ | 37 | list: [ |
| 33 | { | 38 | { |
| 34 | - group: { title: '基础信息', span: 12 }, | 39 | + group: { title: '基础信息' }, |
| 35 | list: [ | 40 | list: [ |
| 36 | { type: 'el-input', label: '名称', key: 'name' }, | 41 | { type: 'el-input', label: '名称', key: 'name' }, |
| 37 | { type: 'el-input-number', label: '年龄', key: 'age' }, | 42 | { type: 'el-input-number', label: '年龄', key: 'age' }, |
| 38 | ], | 43 | ], |
| 39 | }, | 44 | }, |
| 40 | { | 45 | { |
| 41 | - group: { title: '住址', span: 24, key: 'location' }, | 46 | + group: { title: '' }, |
| 47 | + }, | ||
| 48 | + { | ||
| 49 | + group: { title: '住址', key: 'location', span: 24 }, | ||
| 42 | list: [ | 50 | list: [ |
| 43 | { type: 'el-input', label: '地址简称', key: 'locationMin' }, | 51 | { type: 'el-input', label: '地址简称', key: 'locationMin' }, |
| 44 | { | 52 | { |
| 45 | - group: { span: 12, key: 'district' }, | 53 | + group: { key: 'district' }, |
| 46 | list: [ | 54 | list: [ |
| 47 | - { type: 'el-input', label: '省', key: 'province', span: 24, rules: [{ required: true, message: '请输入省' }] }, | ||
| 48 | - { type: 'el-input', label: '市', key: 'city', span: 24 }, | 55 | + { type: 'el-input', label: '省', key: 'province', rules: [{ required: true, message: '请输入省' }], span: 12 }, |
| 56 | + { type: 'el-input', label: '市', key: 'city', span: 12 }, | ||
| 49 | ], | 57 | ], |
| 50 | }, | 58 | }, |
| 51 | { | 59 | { |
| 52 | - group: { title: '小区信息', span: 24 }, | 60 | + group: { title: '小区信息' }, |
| 53 | list: [ | 61 | list: [ |
| 54 | - { type: 'el-input', label: '小区名', key: 'areaName', span: 24 }, | ||
| 55 | - { type: 'el-input', label: '门牌号', key: 'homeNum', span: 24 }, | 62 | + { type: 'el-input', label: '小区名', key: 'areaName', span: 12 }, |
| 63 | + { type: 'el-input', label: '门牌号', key: 'homeNum', span: 12 }, | ||
| 56 | { | 64 | { |
| 57 | - group: { title: 'A栋', span: 24 }, | 65 | + group: { title: 'A栋' }, |
| 58 | list: [ | 66 | list: [ |
| 59 | - { type: 'el-input-number', label: '人数', key: 'anumber', span: 24, | 67 | + { type: 'el-input-number', label: '人数', key: 'anumber', |
| 60 | on: { | 68 | on: { |
| 61 | change(value) { | 69 | change(value) { |
| 62 | console.log(value); | 70 | console.log(value); |
| @@ -66,15 +74,15 @@ export default { | @@ -66,15 +74,15 @@ export default { | ||
| 66 | ], | 74 | ], |
| 67 | }, | 75 | }, |
| 68 | { | 76 | { |
| 69 | - group: { title: 'B栋', span: 24, key: 'bside' }, | 77 | + group: { title: 'B栋', key: 'bside' }, |
| 70 | list: [ | 78 | list: [ |
| 71 | - { type: 'el-input-number', label: '人数', key: 'bnumber', span: 24, | 79 | + { type: 'el-input-number', label: '人数', key: 'bnumber', |
| 72 | on({ model, update }) { | 80 | on({ model, update }) { |
| 73 | return { | 81 | return { |
| 74 | change(value) { | 82 | change(value) { |
| 75 | if (value === 18 && model.age === 18) { | 83 | if (value === 18 && model.age === 18) { |
| 76 | - // TODO update后本值不变的BUG | ||
| 77 | - update({ name: 'location.areaName', value: 'haha' }); | 84 | + console.log(model) |
| 85 | + update({ 'location.areaName': 'hehe', name: 'aaa', 'text.0': 'abc', 'ttt.0': 'abc' }); | ||
| 78 | } | 86 | } |
| 79 | } | 87 | } |
| 80 | } | 88 | } |