大家好!我叫戴向天
QQ群:602504799
如若有不理解的,可加QQ群进行咨询了解
本文内容仅供参考,若想了解并进行相对应的扩展请加入QQ群  
demo
<DynamicForm :config="dynamicConfig" :rules="rules" />
script
data(){
return {
rules: {
carSign: [
{
validator(rule, value, callback) {
const { formData } = this
console.log('触发地点:rules.carSign.validator', value, 'formData=>', formData)
if (value && value.length > 5) {
callback(new Error('字符长度不可超出5个字符'))
} else {
callback()
}
},
trigger: 'change'
}
]
},
dynamicConfig: [
{
gutter: 30,
children: [
[
{
gutter: 10,
children: [
{ type: 'select', label: '车位类别', prop: 'carType' },
{ type: 'select', label: '车位类型', prop: 'carCategory' }
]
},
{
gutter: 10,
children: [
{ label: '车位编号', prop: 'carSign', type: 'input' },
{
type: 'search',
span: 12,
label: '车位区域',
prop: 'carArea',
className: 'hide-suffix',
bind: { readOnly: true },
events: [
{
name: 'click',
async handler({ value, vm }) {
console.log(value,vm)
}
}
],
slots(h) {
return h('span', {
slotName: 'enterButton'
})
}
}
]
}
],
{
tag: 'div',
span: 7,
children: [
{
type: 'button',
prop: 'query',
slots: () => '查询',
bind: { type: 'primary' },
events: [
{
name: 'click',
handler({ vm }) {
vm.setDisabled(true, ['reset', 'query', 'carType'])
}
}
]
},
{ type: 'button', prop: 'reset', slots: () => '重置', bind: {} },
{
type: 'button',
slots: () => '展开',
bind: { type: 'link' },
events: [
{
name: 'click',
handler({ vm }) {
vm.setDisabled(false)
}
}
]
}
]
}
]
}
]
}
}
dynamicForm/index.js
import props from './props'
import methods from './methods'
import componentMapping from './componentMapping'
import { createVnodes, getFormData } from './utils'
export default {
name: 'DynamicForm',
props,
data () {
return {
rightConfig: [],
formUI: 'ant', // 该参数暂时没用,只是单纯的用来提醒
formData: {},
loclParams: {}
}
},
methods: {
...methods,
init () {
this.rightConfig = [].concat(this.config)
const newRules = this.getRules(this.rules)
this.$emit('update:rules', newRules)
}
},
watch: {
config: {
deep: true,
handler () {
this.init()
}
},
rightConfig: {
deep: true,
handler (value) {
this.formData = getFormData(this.rightConfig)
}
}
},
created () {
this.init()
},
mounted () {
this.$nextTick(() => {
// 当表单加载完毕之后进行报出告知
this.$emit('loaded', {
formData: this.formData,
vm: this
})
})
},
render (h) {
const vm = this
const { rightConfig, formData } = vm
const { rules, layout } = vm.$props
const defaultGutter = vm.$props.gutter
return h(componentMapping['form'], {
class: {
'dynamic-form': true
},
attrs: {
model: formData,
rules,
layout
}
}, createVnodes(h, rightConfig, { defaultGutter, vm, formData }))
}
}
dynamicForm/props.js
const $props = {
config: {
type: Array,
default: () => [],
note: '表单的配置信息'
},
rules: {
type: Object,
default: () => ({}),
note: '效验规则,具体的参考相对应的UI框架'
},
gutter: {
type: Number,
default: null,
note: '默认的间距'
},
layout: {
type: String,
types: {
string: ['horizontal', 'vertical', 'inline']
},
default: 'horizontal',
note: '表单布局'
}
}
export default $props
dynamicForm/methods.js
import Moment from 'moment'
const refName = 'form'
const defaultFormat = 'YYYY-MM-DD HH:mm:ss'
export default {
// 获取表单
getForm () {
return this.$refs[refName]
},
/**
* 通过指定的字段名进行获取相对应的数据
* @param {String} fieldName;
* @returns {Any};
*/
getFieldValue (fieldName) {
return this.getForm().getFieldValue(fieldName)
},
/**
* 将规则中validator进行处理进行this指向处理
* 注意的是进行props传参的时候请进行加上sync
* 例如:rules.sync="rulesConfig"
* @param {Object} rules;
* @returns {Object};
*/
getRules (rules = {}) {
return Object.keys(rules).reduce((obj, key) => {
const newRules = [].concat(rules[key])
obj[key] = newRules.map((item) => {
if ('validator' in item && typeof item.validator === 'function') {
const validator = item.validator
item.validator = (...agrs) => {
this.$nextTick(() => {
const newAgrs = [...agrs]
const rule = newAgrs.shift()
newAgrs.shift()
const value = this.formData[key]
validator.call(this, rule, value, ...newAgrs)
})
}
}
return item
})
return obj
}, {})
},
/**
* 判断是不是Moment对象
* @param {Any} data;
* @returns {Boolean};
*/
isMoment (data) {
return typeof data === 'object' && '_isAMomentObject' in data && data._isAMomentObject
},
/**
* 获取最终的表单参数值
* @param {Object} result;
* @returns {Object};
*/
getFieldsValue (result) {
const data = result || this.formData || {}
const keys = Object.keys(data)
return keys.reduce((obj, key) => {
const config = this.getConfig(this.rightConfig, key)
if (config) {
const { bind = {} } = config
const isMoment = this.isMoment(obj[key])
const isArray = Array.isArray(obj[key])
if (isMoment) {
obj[key] = obj[key].format(bind.format || defaultFormat)
} else if (isArray) {
const allIsMoment = obj[key].every(item => this.isMoment(item))
if (allIsMoment) {
obj[key] = obj[key].map(item => item.format(bind.format || defaultFormat))
}
}
}
return obj
}, data)
},
/**
* 表单效验
* @returns {Promise};
*/
validateFields () {
return new Promise((resolve) => {
this.getForm().validate((err, values) => resolve(err ? null : this.getFieldsValue(values)))
})
},
/**
* 获取表单的数据包含有local
* @param {Boolean} bool;
* bool 为true的时候则是带有效验的,为false则是不效验直接获取数据
* @param {Array<String>} filterKeys;
* filterKeys 过滤掉指定的字段信息
* @returns {Object | Promise};
*/
async getParams (bool, filterKeys = []) {
let params = null
if (bool) {
params = await this.validateFields()
} else {
params = this.getFieldsValue()
}
if (params) {
params = {
...params,
...this.getLocalParams()
}
if (filterKeys.length > 0) {
params = Object.keys(params).reduce((obj, key) => {
if (filterKeys.indexOf(key) < 0) {
obj[key] = params[key]
}
return obj
}, {})
}
}
return params
},
/**
* 设置禁用效果
* @param {Boolean} disabled; 默认false
* @param {String | Array<String>} props 默认null 给指定的prop或者指定prop数组进行设置禁用状态,当为null值的时候,则是代表全部prop;
* @returns {this};
*/
setDisabled (disabled = false, props = null) {
let propsArr = Object.keys(this.formData)
if (props) {
props = Array.isArray(props) ? props : [props]
propsArr = propsArr.filter(key => props.indexOf(key) >= 0)
}
propsArr.forEach(prop => {
const config = this.getConfig(this.rightConfig, prop)
config.bind = {
...(config.bind || {}),
disabled
}
this.configChange(this.rightConfig, { prop, config, type: 'replace' })
})
return this
},
/**
* 设置options
* @param {String} propName;
* @param {Array<key,value>} options;
* @returns {this};
*/
setOptions (propName, options) {
const config = this.getConfig(this.rightConfig, propName)
config.bind = {
...(config.bind || {}),
options
}
this.configChange(this.rightConfig, { prop, config, type: 'replace' })
return this
},
/**
* 批量设置options
* @param {Object} obj;
* @returns {this};
*/
setOptionsMap (obj = {}) {
Object.keys(obj).forEach(key => {
if (key in this.formData && Array.isArray(obj[key])) {
this.setOptions(key, obj[key])
}
})
return this
},
// 重置表单
reset () {
this.loclParams = {}
this.getForm().resetFields()
return this
},
/**
* 设置当前form的作用域数据信息
* @param {String} key;
* @param {*} value;
* @returns {this};
*/
setLocalParams (key, value) {
this.$set(this.loclParams, key, value)
return this
},
/**
* 获取当前form的作用域数据信息
* @param {String} key 当key有值的时候将会获取指定的数据,若没有则进行获取作用域的所有数据;
* @returns {*};
*/
getLocalParams (key) {
return key ? this.loclParams[key] : this.loclParams
},
/**
* 设置表单数据 键值对的方式
* @param {String} key;
* @param {Any} value;
* @returns {this};
*/
setData (key, value) {
if (key in this.formData) {
const { type } = this.getConfig(this.rightConfig, key)
if (['date', 'month'].indexOf(type) >= 0) {
value = new Moment(value)
} else if (type === 'range') {
value = (value || []).map(val => new Moment(val))
}
this.formData[key] = value
}
return this
},
/**
* 批量设置表单数据
* @param {Object} obj;
* @returns {this};
*/
setDataMap (obj = {}) {
Object.keys(obj || {}).forEach(key => this.setData(key, obj[key]))
return this
},
/**
* 获取配置信息
* @param {Array} arr;
* @param {String} prop;
* @returns {Object};
*/
getConfig (arr, prop) {
return arr.reduce((obj, item) => {
if (obj) {
return obj
}
const isArray = Array.isArray(item)
if (isArray) {
return this.getConfig(item, prop)
} else if (item.children && Array.isArray(item.children) && item.children.length > 0) {
return this.getConfig(item.children, prop)
} else if (item.prop === prop) {
return item
}
}, null)
},
/**
* 改变配置信息
* @param {Array} arr;
* @param {String} param.prop;
* @param {Object} param.config;
* @param {String} param.type;
* @returns {undefined};
*/
configChange (arr, { prop, config, type }) {
let key = null
arr.forEach((item, index) => {
const isArray = Array.isArray(item)
if (isArray) {
this.configChange(item, { prop, config, type })
} else if (item.children && Array.isArray(item.children) && item.children.length > 0) {
this.configChange(item.children, { prop, config, type })
} else if (item.prop === prop) {
key = index
}
})
if (key !== null) {
if (type === 'replace') {
arr.splice(key, 1, config)
} else if (type === 'delete') {
arr.splice(key, 1)
} else if (type === 'appendBefore') {
arr.splice(key - 2, 0, config)
} else if (type === 'appendAfter') {
arr.splice(key + 1, 0, config)
} else if (type === 'update') {
arr.splice(key, 1, {
...arr[key],
...config
})
}
}
},
/**
* 替换配置信息
* @param {String} prop 指定的字段;
* @param {Object} config 配置信息(同$Props.config的单个配置一致);
* @returns {this};
*/
replace (prop, config) {
if (prop && config) {
this.configChange(this.rightConfig, { prop, config, type: 'replace' })
}
return this
},
/**
* 插入行
* @param {Array<Number>} indexs;
* @param {Array<Object> | Object} config;
* @returns {this};
*/
insert (indexs, config) {
let obj = this.rightConfig
const lastIndex = indexs.pop()
res.forEach(num => { obj = obj[num] })
obj.splice(lastIndex - 1, 0, config)
return this
},
/**
* 删除指定的prop
* @param {String} prop;
* @returns {this};
*/
delete (prop) {
if (prop) {
this.configChange(this.rightConfig, { prop, type: 'delete' })
}
return this
},
/**
* 添加配置信息,该添加是直接进行添加一个一维数组
* @param {Object} config;
* @returns {this};
*/
push (config) {
if (config) {
this.rightConfig.push(config)
}
return this
},
/**
* 添加到指定prop字段前面
* @param {String} prop;
* @param {Object} config;
* @returns {this};
*/
appendBefore (prop, config) {
if (prop && config) {
this.configChange(this.rightConfig, { prop, config, type: 'appendBefore' })
}
return this
},
/**
* 添加到指定prop字段后面
* @param {String} prop;
* @param {Object} config;
* @returns {this};
*/
appendAfter (prop, config) {
if (prop && config) {
this.configChange(this.rightConfig, { prop, config, type: 'appendAfter' })
}
return this
}
}
dynamicForm/componentMapping.js
/**
* 组件名称简写映射配置信息
*/
const componentMapping = {
'form': 'a-form-model',
'form-item': 'a-form-model-item',
'row': 'a-row',
'col': 'a-col',
'input': 'a-input',
'select': 'a-select',
'checkbox': 'a-checkbox-group',
'radio': 'a-radio-group',
'textarea': 'a-textarea',
'upload': 'a-upload-dragger',
'switch': 'a-switch',
'search': 'a-input-search',
'number': 'a-input-number',
'date': 'a-date-picker',
'month': 'a-month-picker',
'range': 'a-range-picker',
'button': 'a-button'
}
export default componentMapping
dynamicForm/utils.js
import componentMapping from './componentMapping'
/**
* 创建事件信息
* @param {VueNode} vm;
* @param {Object} item;
* @returns {Object};
*/
export function createEvent (vm, item) {
const propName = item.prop
const type = item.type
const events = item.events || []
const res = events.reduce((obj, next) => {
const { name, method, handler } = next
if (handler || method) {
const isFn = typeof handler === 'function'
const isStr = typeof method === 'string'
obj[name] = (...args) => {
if (isFn) {
return handler({
value: vm.formData[propName],
config: item,
vm: vm,
args
})
} else if (isStr) {
vm.$parent.operationHandler({
value: vm.formData[propName],
config: item,
vm: vm,
args
})
}
}
}
return obj
}, {})
const data = {
value: vm.formData[propName],
vm,
args: arguments
}
// 以下部分主要是做v-model处理
if (['select', 'date', 'range', 'month'].indexOf(type) >= 0) {
const change = res.change
res.change = (res) => {
vm.formData[propName] = res
if (change) {
change(data)
}
}
} else {
const input = res.input
res.input = (res) => {
const isEvent = res.toString().indexOf('Event') > 0
vm.formData[propName] = isEvent ? res.target.value : res
if (input) {
input(data)
}
}
}
return res
}
/**
* tag标签的子级节点创建
* @param {createElement} h;
* @param {Vue} vm;
* @param {Array} children;
* @returns {Array<vnode>};
*/
export function createTagVnodes (h, vm, children = []) {
return children.map(child => {
const { prop, bind = {}, type, slots, innerHTML } = child
return h(componentMapping[type] || type, {
attrs: {
...(bind || {})
},
domProps: {
innerHTML
},
on: createEvent(vm, child)
}, slots && slots(h))
})
}
/**
* 计算间距
* @param {Array} array;
* @returns {Number};
*/
export function getSpan (array = []) {
const defaultSpan = 24 / array.length
const isAllArray = array.every(node => Array.isArray(node))
if (isAllArray) {
return defaultSpan
}
const surplusSpan = array.filter(item => !Array.isArray(item)).reduce((total, node) => total - (node.span || defaultSpan), 24)
const childrenArray = array.filter(node => Array.isArray(node))
return surplusSpan / childrenArray.length
}
/**
* 获取相对应的placeholder
* @param {String} label;
* @param {String} type;
* @returns {String | Array};
*/
export function getPlaceholder (label, type) {
const isCheck = ['select',
'checkbox',
'radio',
'upload',
'switch',
'search',
'date',
'month'].indexOf(type) > -1
const isRange = type === 'range'
if (isRange) {
return ['开始时间', '结束时间']
}
return (isCheck ? '请选择' : '请输入') + (label || '')
}
/**
* 创建vnode节点
* @param {createElement} h;
* @param {Array} row;
* @param {Number} param.defaultGutter;
* @param {vue} param.vm;
* @param {Object} param.formData;
* @returns {vnodes};
*/
export function createVnodes (h, row = [], { defaultGutter, vm, formData }) {
return row.map((column = {}) => {
const {
children = [], // Array
gutter// row的间距
} = column
const defaultSpan = 24 / children.length
return h(componentMapping['row'], {
attrs: { gutter: gutter || defaultGutter || 0 }
}, children.map(child => {
const isArray = Array.isArray(child)
const { prop = null, span, tag, hideLabel, label, labelCol, wrapperCol, required, type, bind = {}, events = [], slots, className, style } = child
let childVnodes = slots ? slots(h, child) : []
if (!Array.isArray(childVnodes)) {
childVnodes = [childVnodes]
}
return h(componentMapping['col'], {
attrs: {
span: isArray ? getSpan(children) : (span || defaultSpan)
}
}, [isArray ? createVnodes(h, child, { defaultGutter, vm, formData }) : [h(tag || componentMapping['form-item'], {
class: { hideLabel },
attrs: { label: hideLabel ? ' ' : label, labelCol, wrapperCol, required, prop },
style: { ...style }
}, tag ? createTagVnodes(h, vm, child.children) : [
h(componentMapping[type] || type, {
class: className,
attrs: {
...bind,
value: formData[prop],
placeholder: (bind || {}).placeholder || getPlaceholder(label, type)
},
on: createEvent(vm, child)
}, childVnodes)
])]]
)
}))
})
}
/**
* 获取配置参数
* @param {Array} config;
* @returns {Object};
*/
export function getFormData (config = []) {
return config.reduce((obj, next) => {
const isArray = Array.isArray(next)
const hasChildren = next.children && Array.isArray(next.children)
if (isArray) {
obj = {
...obj,
...getFormData(next)
}
} else if (hasChildren) {
obj = {
...obj,
...getFormData(next.children)
}
} else if (next.prop) {
obj[next.prop] = undefined
}
return obj
}, {})
}
ant-design的辅助样式代码
.dynamic-form {
&.ant-form-inline {
.ant-form-item {
display: flex;
margin-bottom: 24px;
.ant-form-item-label {
flex-shrink: 0;
}
.ant-form-item-control-wrapper {
flex-grow: 1;
}
}
}
.ant-col {
overflow: hidden;
.ant-form-item {
width: 100%;
}
}
// 隐藏表单标签的文字
.ant-row.ant-form-item.hideLabel > .ant-form-item-label > label {
color: transparent;
}
.ant-form-item {
display: flex;
.ant-form-item-control-wrapper {
flex-grow: 1;
}
}
.ant-form-item-control > span,
.ant-form-item-control > span > span {
width: 100%;
display: inline-block;
}
.hide-suffix > span.ant-input-suffix {
display: none;
}
.hide-suffix > input.ant-input {
padding-right: 11px !important;
}
}
|