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 | 1 | <template> |
| 2 | 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 | 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 | 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 | 12 | </el-row> |
| 13 | 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 | 16 | <el-form-item :label="item.label" :label-width="item.labelWidth || '120px'" :prop="item.fullKey" :rules="item.rules"> |
| 17 | 17 | <!-- 自定义组件 --> |
| 18 | 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 | 32 | itemKey: String, |
| 33 | 33 | titleClass: String, |
| 34 | 34 | contentClass: String, |
| 35 | + span: Number, | |
| 35 | 36 | }, |
| 36 | 37 | methods: { |
| 37 | 38 | /** |
| ... | ... | @@ -92,7 +93,7 @@ export default { |
| 92 | 93 | bindItemEvent(item) { |
| 93 | 94 | if (item.on) { |
| 94 | 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 | 97 | } else { |
| 97 | 98 | return item.on |
| 98 | 99 | } | ... | ... |
examples/views/page/form-new/index.vue
| 1 | 1 | <template> |
| 2 | 2 | <el-form ref="form" size="small" :class="formClass" :model="formModel"> |
| 3 | 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 | 6 | {{ list }} |
| 6 | 7 | </el-form> |
| 7 | 8 | </template> |
| 8 | 9 | |
| 9 | 10 | <script> |
| 10 | 11 | import FormRender from './form-render'; |
| 11 | -import { cloneDeep, set } from 'lodash'; | |
| 12 | +import { cloneDeep, set } from './util'; | |
| 12 | 13 | |
| 13 | 14 | export default { |
| 14 | 15 | name: 'FormNew', |
| ... | ... | @@ -19,40 +20,49 @@ export default { |
| 19 | 20 | formClass: String, |
| 20 | 21 | titleClass: String, |
| 21 | 22 | contentClass: String, |
| 23 | + span: { | |
| 24 | + type: Number, | |
| 25 | + default: 24 | |
| 26 | + } | |
| 22 | 27 | }, |
| 23 | 28 | data() { |
| 24 | 29 | return { |
| 25 | 30 | model: {}, |
| 26 | 31 | formModel: {}, |
| 32 | + formList: [] | |
| 27 | 33 | } |
| 28 | 34 | }, |
| 29 | 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 | 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 | 68 | methods: { |
| ... | ... | @@ -100,13 +110,17 @@ export default { |
| 100 | 110 | }, |
| 101 | 111 | /** |
| 102 | 112 | * @description 手动更新某一表单项的值 |
| 103 | - * @param {Object} { name => 表单项key,可嵌套; value => 更新的值 } | |
| 113 | + * @param {Object} param 需要更新的参数对象或者对象数组 { name => 表单项key,可嵌套; value => 更新的值 } | |
| 104 | 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 @@ |
| 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 | 69 | \ No newline at end of file | ... | ... |
examples/views/page/other.vue
| ... | ... | @@ -15,7 +15,7 @@ |
| 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" 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 | 19 | </div> |
| 20 | 20 | </template> |
| 21 | 21 | |
| ... | ... | @@ -27,36 +27,44 @@ export default { |
| 27 | 27 | components: { EgForm }, |
| 28 | 28 | data() { |
| 29 | 29 | return { |
| 30 | - model: {}, | |
| 30 | + model: { | |
| 31 | + name: 'name', | |
| 32 | + location: { | |
| 33 | + areaName: 'hahhhahaa', | |
| 34 | + }, | |
| 35 | + }, | |
| 31 | 36 | option: { |
| 32 | 37 | list: [ |
| 33 | 38 | { |
| 34 | - group: { title: '基础信息', span: 12 }, | |
| 39 | + group: { title: '基础信息' }, | |
| 35 | 40 | list: [ |
| 36 | 41 | { type: 'el-input', label: '名称', key: 'name' }, |
| 37 | 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 | 50 | list: [ |
| 43 | 51 | { type: 'el-input', label: '地址简称', key: 'locationMin' }, |
| 44 | 52 | { |
| 45 | - group: { span: 12, key: 'district' }, | |
| 53 | + group: { key: 'district' }, | |
| 46 | 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 | 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 | 66 | list: [ |
| 59 | - { type: 'el-input-number', label: '人数', key: 'anumber', span: 24, | |
| 67 | + { type: 'el-input-number', label: '人数', key: 'anumber', | |
| 60 | 68 | on: { |
| 61 | 69 | change(value) { |
| 62 | 70 | console.log(value); |
| ... | ... | @@ -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 | 78 | list: [ |
| 71 | - { type: 'el-input-number', label: '人数', key: 'bnumber', span: 24, | |
| 79 | + { type: 'el-input-number', label: '人数', key: 'bnumber', | |
| 72 | 80 | on({ model, update }) { |
| 73 | 81 | return { |
| 74 | 82 | change(value) { |
| 75 | 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 | } | ... | ... |