398 lines
10 KiB
Vue
398 lines
10 KiB
Vue
<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 宽度,默认 70px
|
||
* @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>
|