You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
10 KiB
397 lines
10 KiB
<template> |
|
<view class="uni-forms"> |
|
<form> |
|
<slot></slot> |
|
</form> |
|
</view> |
|
</template> |
|
|
|
<script> |
|
import Validator from './validate.js'; |
|
import { |
|
deepCopy, |
|
getValue, |
|
isRequiredField, |
|
setDataValue, |
|
getDataValue, |
|
realName, |
|
isRealName, |
|
rawData, |
|
isEqual |
|
} from './utils.js' |
|
|
|
// #ifndef VUE3 |
|
// 后续会慢慢废弃这个方法 |
|
import Vue from 'vue'; |
|
Vue.prototype.binddata = function(name, value, formName) { |
|
if (formName) { |
|
this.$refs[formName].setValue(name, value); |
|
} else { |
|
let formVm; |
|
for (let i in this.$refs) { |
|
const vm = this.$refs[i]; |
|
if (vm && vm.$options && vm.$options.name === 'uniForms') { |
|
formVm = vm; |
|
break; |
|
} |
|
} |
|
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性'); |
|
formVm.setValue(name, value); |
|
} |
|
}; |
|
// #endif |
|
/** |
|
* Forms 表单 |
|
* @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据 |
|
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773 |
|
* @property {Object} rules 表单校验规则 |
|
* @property {String} validateTrigger = [bind|submit|blur] 校验触发器方式 默认 submit |
|
* @value bind 发生变化时触发 |
|
* @value submit 提交时触发 |
|
* @value blur 失去焦点时触发 |
|
* @property {String} labelPosition = [top|left] label 位置 默认 left |
|
* @value top 顶部显示 label |
|
* @value left 左侧显示 label |
|
* @property {String} labelWidth label 宽度,默认 65px |
|
* @property {String} labelAlign = [left|center|right] label 居中方式 默认 left |
|
* @value left label 左侧显示 |
|
* @value center label 居中 |
|
* @value right label 右侧对齐 |
|
* @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式 |
|
* @value undertext 错误信息在底部显示 |
|
* @value toast 错误信息toast显示 |
|
* @value modal 错误信息modal显示 |
|
* @event {Function} submit 提交时触发 |
|
* @event {Function} validate 校验结果发生变化触发 |
|
*/ |
|
export default { |
|
name: 'uniForms', |
|
emits: ['validate', 'submit'], |
|
options: { |
|
virtualHost: true |
|
}, |
|
props: { |
|
// 即将弃用 |
|
value: { |
|
type: Object, |
|
default () { |
|
return null; |
|
} |
|
}, |
|
// vue3 替换 value 属性 |
|
modelValue: { |
|
type: Object, |
|
default () { |
|
return null; |
|
} |
|
}, |
|
// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue |
|
model: { |
|
type: Object, |
|
default () { |
|
return null; |
|
} |
|
}, |
|
// 表单校验规则 |
|
rules: { |
|
type: Object, |
|
default () { |
|
return {}; |
|
} |
|
}, |
|
//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal] |
|
errShowType: { |
|
type: String, |
|
default: 'undertext' |
|
}, |
|
// 校验触发器方式 默认 bind 取值 [bind|submit] |
|
validateTrigger: { |
|
type: String, |
|
default: 'submit' |
|
}, |
|
// label 位置,默认 left 取值 top/left |
|
labelPosition: { |
|
type: String, |
|
default: 'left' |
|
}, |
|
// label 宽度 |
|
labelWidth: { |
|
type: [String, Number], |
|
default: '' |
|
}, |
|
// label 居中方式,默认 left 取值 left/center/right |
|
labelAlign: { |
|
type: String, |
|
default: 'left' |
|
}, |
|
border: { |
|
type: Boolean, |
|
default: false |
|
} |
|
}, |
|
provide() { |
|
return { |
|
uniForm: this |
|
} |
|
}, |
|
data() { |
|
return { |
|
// 表单本地值的记录,不应该与传如的值进行关联 |
|
formData: {}, |
|
formRules: {} |
|
}; |
|
}, |
|
computed: { |
|
// 计算数据源变化的 |
|
localData() { |
|
const localVal = this.model || this.modelValue || this.value |
|
if (localVal) { |
|
return deepCopy(localVal) |
|
} |
|
return {} |
|
} |
|
}, |
|
watch: { |
|
// 监听数据变化 ,暂时不使用,需要单独赋值 |
|
// localData: {}, |
|
// 监听规则变化 |
|
rules: { |
|
handler: function(val, oldVal) { |
|
this.setRules(val) |
|
}, |
|
deep: true, |
|
immediate: true |
|
} |
|
}, |
|
created() { |
|
// #ifdef VUE3 |
|
let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata |
|
if (!getbinddata) { |
|
getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) { |
|
if (formName) { |
|
this.$refs[formName].setValue(name, value); |
|
} else { |
|
let formVm; |
|
for (let i in this.$refs) { |
|
const vm = this.$refs[i]; |
|
if (vm && vm.$options && vm.$options.name === 'uniForms') { |
|
formVm = vm; |
|
break; |
|
} |
|
} |
|
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性'); |
|
formVm.setValue(name, value); |
|
} |
|
} |
|
} |
|
// #endif |
|
|
|
// 子组件实例数组 |
|
this.childrens = [] |
|
// TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错 |
|
this.inputChildrens = [] |
|
this.setRules(this.rules) |
|
}, |
|
methods: { |
|
/** |
|
* 外部调用方法 |
|
* 设置规则 ,主要用于小程序自定义检验规则 |
|
* @param {Array} rules 规则源数据 |
|
*/ |
|
setRules(rules) { |
|
// TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖 |
|
this.formRules = Object.assign({}, this.formRules, rules) |
|
// 初始化校验函数 |
|
this.validator = new Validator(rules); |
|
}, |
|
|
|
/** |
|
* 外部调用方法 |
|
* 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用 |
|
* @param {Object} key |
|
* @param {Object} value |
|
*/ |
|
setValue(key, value) { |
|
let example = this.childrens.find(child => child.name === key); |
|
if (!example) return null; |
|
this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || []) |
|
return example.onFieldChange(this.formData[key]); |
|
}, |
|
|
|
/** |
|
* 外部调用方法 |
|
* 手动提交校验表单 |
|
* 对整个表单进行校验的方法,参数为一个回调函数。 |
|
* @param {Array} keepitem 保留不参与校验的字段 |
|
* @param {type} callback 方法回调 |
|
*/ |
|
validate(keepitem, callback) { |
|
return this.checkAll(this.formData, keepitem, callback); |
|
}, |
|
|
|
/** |
|
* 外部调用方法 |
|
* 部分表单校验 |
|
* @param {Array|String} props 需要校验的字段 |
|
* @param {Function} 回调函数 |
|
*/ |
|
validateField(props = [], callback) { |
|
props = [].concat(props); |
|
let invalidFields = {}; |
|
this.childrens.forEach(item => { |
|
const name = realName(item.name) |
|
if (props.indexOf(name) !== -1) { |
|
invalidFields = Object.assign({}, invalidFields, { |
|
[name]: this.formData[name] |
|
}); |
|
} |
|
}); |
|
return this.checkAll(invalidFields, [], callback); |
|
}, |
|
|
|
/** |
|
* 外部调用方法 |
|
* 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 |
|
* @param {Array|String} props 需要移除校验的字段 ,不填为所有 |
|
*/ |
|
clearValidate(props = []) { |
|
props = [].concat(props); |
|
this.childrens.forEach(item => { |
|
if (props.length === 0) { |
|
item.errMsg = ''; |
|
} else { |
|
const name = realName(item.name) |
|
if (props.indexOf(name) !== -1) { |
|
item.errMsg = ''; |
|
} |
|
} |
|
}); |
|
}, |
|
|
|
/** |
|
* 外部调用方法 ,即将废弃 |
|
* 手动提交校验表单 |
|
* 对整个表单进行校验的方法,参数为一个回调函数。 |
|
* @param {Array} keepitem 保留不参与校验的字段 |
|
* @param {type} callback 方法回调 |
|
*/ |
|
submit(keepitem, callback, type) { |
|
for (let i in this.dataValue) { |
|
const itemData = this.childrens.find(v => v.name === i); |
|
if (itemData) { |
|
if (this.formData[i] === undefined) { |
|
this.formData[i] = this._getValue(i, this.dataValue[i]); |
|
} |
|
} |
|
} |
|
|
|
if (!type) { |
|
console.warn('submit 方法即将废弃,请使用validate方法代替!'); |
|
} |
|
|
|
return this.checkAll(this.formData, keepitem, callback, 'submit'); |
|
}, |
|
|
|
// 校验所有 |
|
async checkAll(invalidFields, keepitem, callback, type) { |
|
// 不存在校验规则 ,则停止校验流程 |
|
if (!this.validator) return |
|
let childrens = [] |
|
// 处理参与校验的item实例 |
|
for (let i in invalidFields) { |
|
const item = this.childrens.find(v => realName(v.name) === i) |
|
if (item) { |
|
childrens.push(item) |
|
} |
|
} |
|
|
|
// 如果validate第一个参数是funciont ,那就走回调 |
|
if (!callback && typeof keepitem === 'function') { |
|
callback = keepitem; |
|
} |
|
|
|
let promise; |
|
// 如果不存在回调,那么使用 Promise 方式返回 |
|
if (!callback && typeof callback !== 'function' && Promise) { |
|
promise = new Promise((resolve, reject) => { |
|
callback = function(valid, invalidFields) { |
|
!valid ? resolve(invalidFields) : reject(valid); |
|
}; |
|
}); |
|
} |
|
|
|
let results = []; |
|
// 避免引用错乱 ,建议拷贝对象处理 |
|
let tempFormData = JSON.parse(JSON.stringify(invalidFields)) |
|
// 所有子组件参与校验,使用 for 可以使用 awiat |
|
for (let i in childrens) { |
|
const child = childrens[i] |
|
let name = realName(child.name); |
|
const result = await child.onFieldChange(tempFormData[name]); |
|
if (result) { |
|
results.push(result); |
|
// toast ,modal 只需要执行第一次就可以 |
|
if (this.errShowType === 'toast' || this.errShowType === 'modal') break; |
|
} |
|
} |
|
|
|
|
|
if (Array.isArray(results)) { |
|
if (results.length === 0) results = null; |
|
} |
|
if (Array.isArray(keepitem)) { |
|
keepitem.forEach(v => { |
|
let vName = realName(v); |
|
let value = getDataValue(v, this.localData) |
|
if (value !== undefined) { |
|
tempFormData[vName] = value |
|
} |
|
}); |
|
} |
|
|
|
// TODO submit 即将废弃 |
|
if (type === 'submit') { |
|
this.$emit('submit', { |
|
detail: { |
|
value: tempFormData, |
|
errors: results |
|
} |
|
}); |
|
} else { |
|
this.$emit('validate', results); |
|
} |
|
|
|
// const resetFormData = rawData(tempFormData, this.localData, this.name) |
|
let resetFormData = {} |
|
resetFormData = rawData(tempFormData, this.name) |
|
callback && typeof callback === 'function' && callback(results, resetFormData); |
|
|
|
if (promise && callback) { |
|
return promise; |
|
} else { |
|
return null; |
|
} |
|
|
|
}, |
|
|
|
/** |
|
* 返回validate事件 |
|
* @param {Object} result |
|
*/ |
|
validateCheck(result) { |
|
this.$emit('validate', result); |
|
}, |
|
_getValue: getValue, |
|
_isRequiredField: isRequiredField, |
|
_setDataValue: setDataValue, |
|
_getDataValue: getDataValue, |
|
_realName: realName, |
|
_isRealName: isRealName, |
|
_isEqual: isEqual |
|
} |
|
}; |
|
</script> |
|
|
|
<style lang="scss"> |
|
.uni-forms {} |
|
</style>
|
|
|