mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-01 02:38:43 +08:00 
			
		
		
		
	初始化管理后台的 uniapp 版本
This commit is contained in:
		| @@ -0,0 +1,397 @@ | ||||
| <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> | ||||
| @@ -0,0 +1,293 @@ | ||||
| /** | ||||
|  * 简单处理对象拷贝 | ||||
|  * @param {Obejct} 被拷贝对象 | ||||
|  * @@return {Object} 拷贝对象 | ||||
|  */ | ||||
| export const deepCopy = (val) => { | ||||
| 	return JSON.parse(JSON.stringify(val)) | ||||
| } | ||||
| /** | ||||
|  * 过滤数字类型 | ||||
|  * @param {String} format 数字类型 | ||||
|  * @@return {Boolean} 返回是否为数字类型 | ||||
|  */ | ||||
| export const typeFilter = (format) => { | ||||
| 	return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined | ||||
|  * @param {String} key 字段名 | ||||
|  * @param {any} value 字段值 | ||||
|  * @param {Object} rules 表单校验规则 | ||||
|  */ | ||||
| export const getValue = (key, value, rules) => { | ||||
| 	const isRuleNumType = rules.find(val => val.format && typeFilter(val.format)); | ||||
| 	const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool'); | ||||
| 	// 输入类型为 number | ||||
| 	if (!!isRuleNumType) { | ||||
| 		if (!value && value !== 0) { | ||||
| 			value = null | ||||
| 		} else { | ||||
| 			value = isNumber(Number(value)) ? Number(value) : value | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 输入类型为 boolean | ||||
| 	if (!!isRuleBoolType) { | ||||
| 		value = isBoolean(value) ? value : false | ||||
| 	} | ||||
|  | ||||
| 	return value; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 获取表单数据 | ||||
|  * @param {String|Array} name 真实名称,需要使用 realName 获取 | ||||
|  * @param {Object} data 原始数据 | ||||
|  * @param {any} value  需要设置的值 | ||||
|  */ | ||||
| export const setDataValue = (field, formdata, value) => { | ||||
| 	formdata[field] = value | ||||
| 	return value || '' | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 获取表单数据 | ||||
|  * @param {String|Array} field 真实名称,需要使用 realName 获取 | ||||
|  * @param {Object} data 原始数据 | ||||
|  */ | ||||
| export const getDataValue = (field, data) => { | ||||
| 	return objGet(data, field) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 获取表单类型 | ||||
|  * @param {String|Array} field 真实名称,需要使用 realName 获取 | ||||
|  */ | ||||
| export const getDataValueType = (field, data) => { | ||||
| 	const value = getDataValue(field, data) | ||||
| 	return { | ||||
| 		type: type(value), | ||||
| 		value | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 获取表单可用的真实name | ||||
|  * @param {String|Array} name 表单name | ||||
|  * @@return {String} 表单可用的真实name | ||||
|  */ | ||||
| export const realName = (name, data = {}) => { | ||||
| 	const base_name = _basePath(name) | ||||
| 	if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) { | ||||
| 		const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_') | ||||
| 		return realname | ||||
| 	} | ||||
| 	return base_name[0] || name | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 判断是否表单可用的真实name | ||||
|  * @param {String|Array} name 表单name | ||||
|  * @@return {String} 表单可用的真实name | ||||
|  */ | ||||
| export const isRealName = (name) => { | ||||
| 	const reg = /^_formdata_#*/ | ||||
| 	return reg.test(name) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 获取表单数据的原始格式 | ||||
|  * @@return {Object|Array} object 需要解析的数据 | ||||
|  */ | ||||
| export const rawData = (object = {}, name) => { | ||||
| 	let newData = JSON.parse(JSON.stringify(object)) | ||||
| 	let formData = {} | ||||
| 	for(let i in newData){ | ||||
| 		let path = name2arr(i) | ||||
| 		objSet(formData,path,newData[i]) | ||||
| 	} | ||||
| 	return formData | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 真实name还原为 array | ||||
|  * @param {*} name  | ||||
|  */ | ||||
| export const name2arr = (name) => { | ||||
| 	let field = name.replace('_formdata_#', '') | ||||
| 	field = field.split('#').map(v => (isNumber(v) ? Number(v) : v)) | ||||
| 	return field | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 对象中设置值 | ||||
|  * @param {Object|Array} object 源数据 | ||||
|  * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] | ||||
|  * @param {String} value 需要设置的值 | ||||
|  */ | ||||
| export const objSet = (object, path, value) => { | ||||
| 	if (typeof object !== 'object') return object; | ||||
| 	_basePath(path).reduce((o, k, i, _) => { | ||||
| 		if (i === _.length - 1) {  | ||||
| 			// 若遍历结束直接赋值 | ||||
| 			o[k] = value | ||||
| 			return null | ||||
| 		} else if (k in o) {  | ||||
| 			// 若存在对应路径,则返回找到的对象,进行下一次遍历 | ||||
| 			return o[k] | ||||
| 		} else {  | ||||
| 			// 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象 | ||||
| 			o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {} | ||||
| 			return o[k] | ||||
| 		} | ||||
| 	}, object) | ||||
| 	// 返回object | ||||
| 	return object; | ||||
| } | ||||
|  | ||||
| // 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用 | ||||
| function _basePath(path) { | ||||
| 	// 若是数组,则直接返回 | ||||
| 	if (Array.isArray(path)) return path | ||||
| 	// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']' | ||||
| 	return path.replace(/\[/g, '.').replace(/\]/g, '').split('.') | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 从对象中获取值 | ||||
|  * @param {Object|Array} object 源数据 | ||||
|  * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] | ||||
|  * @param {String} defaultVal 如果无法从调用链中获取值的默认值 | ||||
|  */ | ||||
| export const objGet = (object, path, defaultVal = 'undefined') => { | ||||
| 	// 先将path处理成统一格式 | ||||
| 	let newPath = _basePath(path) | ||||
| 	// 递归处理,返回最后结果 | ||||
| 	let val = newPath.reduce((o, k) => { | ||||
| 		return (o || {})[k] | ||||
| 	}, object); | ||||
| 	return !val || val !== undefined ? val : defaultVal | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 是否为 number 类型  | ||||
|  * @param {any} num 需要判断的值 | ||||
|  * @return {Boolean} 是否为 number | ||||
|  */ | ||||
| export const isNumber = (num) => { | ||||
| 	return !isNaN(Number(num)) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 是否为 boolean 类型  | ||||
|  * @param {any} bool 需要判断的值 | ||||
|  * @return {Boolean} 是否为 boolean | ||||
|  */ | ||||
| export const isBoolean = (bool) => { | ||||
| 	return (typeof bool === 'boolean') | ||||
| } | ||||
| /** | ||||
|  * 是否有必填字段 | ||||
|  * @param {Object} rules 规则 | ||||
|  * @return {Boolean} 是否有必填字段 | ||||
|  */ | ||||
| export const isRequiredField = (rules) => { | ||||
| 	let isNoField = false; | ||||
| 	for (let i = 0; i < rules.length; i++) { | ||||
| 		const ruleData = rules[i]; | ||||
| 		if (ruleData.required) { | ||||
| 			isNoField = true; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	return isNoField; | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 获取数据类型 | ||||
|  * @param {Any} obj 需要获取数据类型的值 | ||||
|  */ | ||||
| export const type = (obj) => { | ||||
| 	var class2type = {}; | ||||
|  | ||||
| 	// 生成class2type映射 | ||||
| 	"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) { | ||||
| 		class2type["[object " + item + "]"] = item.toLowerCase(); | ||||
| 	}) | ||||
| 	if (obj == null) { | ||||
| 		return obj + ""; | ||||
| 	} | ||||
| 	return typeof obj === "object" || typeof obj === "function" ? | ||||
| 		class2type[Object.prototype.toString.call(obj)] || "object" : | ||||
| 		typeof obj; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 判断两个值是否相等 | ||||
|  * @param {any} a 值   | ||||
|  * @param {any} b 值   | ||||
|  * @return {Boolean} 是否相等 | ||||
|  */ | ||||
| export const isEqual = (a, b) => { | ||||
| 	//如果a和b本来就全等 | ||||
| 	if (a === b) { | ||||
| 		//判断是否为0和-0 | ||||
| 		return a !== 0 || 1 / a === 1 / b; | ||||
| 	} | ||||
| 	//判断是否为null和undefined | ||||
| 	if (a == null || b == null) { | ||||
| 		return a === b; | ||||
| 	} | ||||
| 	//接下来判断a和b的数据类型 | ||||
| 	var classNameA = toString.call(a), | ||||
| 		classNameB = toString.call(b); | ||||
| 	//如果数据类型不相等,则返回false | ||||
| 	if (classNameA !== classNameB) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	//如果数据类型相等,再根据不同数据类型分别判断 | ||||
| 	switch (classNameA) { | ||||
| 		case '[object RegExp]': | ||||
| 		case '[object String]': | ||||
| 			//进行字符串转换比较 | ||||
| 			return '' + a === '' + b; | ||||
| 		case '[object Number]': | ||||
| 			//进行数字转换比较,判断是否为NaN | ||||
| 			if (+a !== +a) { | ||||
| 				return +b !== +b; | ||||
| 			} | ||||
| 			//判断是否为0或-0 | ||||
| 			return +a === 0 ? 1 / +a === 1 / b : +a === +b; | ||||
| 		case '[object Date]': | ||||
| 		case '[object Boolean]': | ||||
| 			return +a === +b; | ||||
| 	} | ||||
| 	//如果是对象类型 | ||||
| 	if (classNameA == '[object Object]') { | ||||
| 		//获取a和b的属性长度 | ||||
| 		var propsA = Object.getOwnPropertyNames(a), | ||||
| 			propsB = Object.getOwnPropertyNames(b); | ||||
| 		if (propsA.length != propsB.length) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		for (var i = 0; i < propsA.length; i++) { | ||||
| 			var propName = propsA[i]; | ||||
| 			//如果对应属性对应值不相等,则返回false | ||||
| 			if (a[propName] !== b[propName]) { | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
| 	//如果是数组类型 | ||||
| 	if (classNameA == '[object Array]') { | ||||
| 		if (a.toString() == b.toString()) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,486 @@ | ||||
| var pattern = { | ||||
| 	email: /^\S+?@\S+?\.\S+?$/, | ||||
| 	idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, | ||||
| 	url: new RegExp( | ||||
| 		"^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", | ||||
| 		'i') | ||||
| }; | ||||
|  | ||||
| const FORMAT_MAPPING = { | ||||
| 	"int": 'integer', | ||||
| 	"bool": 'boolean', | ||||
| 	"double": 'number', | ||||
| 	"long": 'number', | ||||
| 	"password": 'string' | ||||
| 	// "fileurls": 'array' | ||||
| } | ||||
|  | ||||
| function formatMessage(args, resources = '') { | ||||
| 	var defaultMessage = ['label'] | ||||
| 	defaultMessage.forEach((item) => { | ||||
| 		if (args[item] === undefined) { | ||||
| 			args[item] = '' | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	let str = resources | ||||
| 	for (let key in args) { | ||||
| 		let reg = new RegExp('{' + key + '}') | ||||
| 		str = str.replace(reg, args[key]) | ||||
| 	} | ||||
| 	return str | ||||
| } | ||||
|  | ||||
| function isEmptyValue(value, type) { | ||||
| 	if (value === undefined || value === null) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	if (typeof value === 'string' && !value) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	if (Array.isArray(value) && !value.length) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	if (type === 'object' && !Object.keys(value).length) { | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| const types = { | ||||
| 	integer(value) { | ||||
| 		return types.number(value) && parseInt(value, 10) === value; | ||||
| 	}, | ||||
| 	string(value) { | ||||
| 		return typeof value === 'string'; | ||||
| 	}, | ||||
| 	number(value) { | ||||
| 		if (isNaN(value)) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		return typeof value === 'number'; | ||||
| 	}, | ||||
| 	"boolean": function(value) { | ||||
| 		return typeof value === 'boolean'; | ||||
| 	}, | ||||
| 	"float": function(value) { | ||||
| 		return types.number(value) && !types.integer(value); | ||||
| 	}, | ||||
| 	array(value) { | ||||
| 		return Array.isArray(value); | ||||
| 	}, | ||||
| 	object(value) { | ||||
| 		return typeof value === 'object' && !types.array(value); | ||||
| 	}, | ||||
| 	date(value) { | ||||
| 		return value instanceof Date; | ||||
| 	}, | ||||
| 	timestamp(value) { | ||||
| 		if (!this.integer(value) || Math.abs(value).toString().length > 16) { | ||||
| 			return false | ||||
| 		} | ||||
| 		return true; | ||||
| 	}, | ||||
| 	file(value) { | ||||
| 		return typeof value.url === 'string'; | ||||
| 	}, | ||||
| 	email(value) { | ||||
| 		return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; | ||||
| 	}, | ||||
| 	url(value) { | ||||
| 		return typeof value === 'string' && !!value.match(pattern.url); | ||||
| 	}, | ||||
| 	pattern(reg, value) { | ||||
| 		try { | ||||
| 			return new RegExp(reg).test(value); | ||||
| 		} catch (e) { | ||||
| 			return false; | ||||
| 		} | ||||
| 	}, | ||||
| 	method(value) { | ||||
| 		return typeof value === 'function'; | ||||
| 	}, | ||||
| 	idcard(value) { | ||||
| 		return typeof value === 'string' && !!value.match(pattern.idcard); | ||||
| 	}, | ||||
| 	'url-https'(value) { | ||||
| 		return this.url(value) && value.startsWith('https://'); | ||||
| 	}, | ||||
| 	'url-scheme'(value) { | ||||
| 		return value.startsWith('://'); | ||||
| 	}, | ||||
| 	'url-web'(value) { | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| class RuleValidator { | ||||
|  | ||||
| 	constructor(message) { | ||||
| 		this._message = message | ||||
| 	} | ||||
|  | ||||
| 	async validateRule(fieldKey, fieldValue, value, data, allData) { | ||||
| 		var result = null | ||||
|  | ||||
| 		let rules = fieldValue.rules | ||||
|  | ||||
| 		let hasRequired = rules.findIndex((item) => { | ||||
| 			return item.required | ||||
| 		}) | ||||
| 		if (hasRequired < 0) { | ||||
| 			if (value === null || value === undefined) { | ||||
| 				return result | ||||
| 			} | ||||
| 			if (typeof value === 'string' && !value.length) { | ||||
| 				return result | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		var message = this._message | ||||
|  | ||||
| 		if (rules === undefined) { | ||||
| 			return message['default'] | ||||
| 		} | ||||
|  | ||||
| 		for (var i = 0; i < rules.length; i++) { | ||||
| 			let rule = rules[i] | ||||
| 			let vt = this._getValidateType(rule) | ||||
|  | ||||
| 			Object.assign(rule, { | ||||
| 				label: fieldValue.label || `["${fieldKey}"]` | ||||
| 			}) | ||||
|  | ||||
| 			if (RuleValidatorHelper[vt]) { | ||||
| 				result = RuleValidatorHelper[vt](rule, value, message) | ||||
| 				if (result != null) { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (rule.validateExpr) { | ||||
| 				let now = Date.now() | ||||
| 				let resultExpr = rule.validateExpr(value, allData, now) | ||||
| 				if (resultExpr === false) { | ||||
| 					result = this._getMessage(rule, rule.errorMessage || this._message['default']) | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (rule.validateFunction) { | ||||
| 				result = await this.validateFunction(rule, value, data, allData, vt) | ||||
| 				if (result !== null) { | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (result !== null) { | ||||
| 			result = message.TAG + result | ||||
| 		} | ||||
|  | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	async validateFunction(rule, value, data, allData, vt) { | ||||
| 		let result = null | ||||
| 		try { | ||||
| 			let callbackMessage = null | ||||
| 			const res = await rule.validateFunction(rule, value, allData || data, (message) => { | ||||
| 				callbackMessage = message | ||||
| 			}) | ||||
| 			if (callbackMessage || (typeof res === 'string' && res) || res === false) { | ||||
| 				result = this._getMessage(rule, callbackMessage || res, vt) | ||||
| 			} | ||||
| 		} catch (e) { | ||||
| 			result = this._getMessage(rule, e.message, vt) | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	_getMessage(rule, message, vt) { | ||||
| 		return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) | ||||
| 	} | ||||
|  | ||||
| 	_getValidateType(rule) { | ||||
| 		var result = '' | ||||
| 		if (rule.required) { | ||||
| 			result = 'required' | ||||
| 		} else if (rule.format) { | ||||
| 			result = 'format' | ||||
| 		} else if (rule.arrayType) { | ||||
| 			result = 'arrayTypeFormat' | ||||
| 		} else if (rule.range) { | ||||
| 			result = 'range' | ||||
| 		} else if (rule.maximum !== undefined || rule.minimum !== undefined) { | ||||
| 			result = 'rangeNumber' | ||||
| 		} else if (rule.maxLength !== undefined || rule.minLength !== undefined) { | ||||
| 			result = 'rangeLength' | ||||
| 		} else if (rule.pattern) { | ||||
| 			result = 'pattern' | ||||
| 		} else if (rule.validateFunction) { | ||||
| 			result = 'validateFunction' | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const RuleValidatorHelper = { | ||||
| 	required(rule, value, message) { | ||||
| 		if (rule.required && isEmptyValue(value, rule.format || typeof value)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message.required); | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	range(rule, value, message) { | ||||
| 		const { | ||||
| 			range, | ||||
| 			errorMessage | ||||
| 		} = rule; | ||||
|  | ||||
| 		let list = new Array(range.length); | ||||
| 		for (let i = 0; i < range.length; i++) { | ||||
| 			const item = range[i]; | ||||
| 			if (types.object(item) && item.value !== undefined) { | ||||
| 				list[i] = item.value; | ||||
| 			} else { | ||||
| 				list[i] = item; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		let result = false | ||||
| 		if (Array.isArray(value)) { | ||||
| 			result = (new Set(value.concat(list)).size === list.length); | ||||
| 		} else { | ||||
| 			if (list.indexOf(value) > -1) { | ||||
| 				result = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (!result) { | ||||
| 			return formatMessage(rule, errorMessage || message['enum']); | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	rangeNumber(rule, value, message) { | ||||
| 		if (!types.number(value)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); | ||||
| 		} | ||||
|  | ||||
| 		let { | ||||
| 			minimum, | ||||
| 			maximum, | ||||
| 			exclusiveMinimum, | ||||
| 			exclusiveMaximum | ||||
| 		} = rule; | ||||
| 		let min = exclusiveMinimum ? value <= minimum : value < minimum; | ||||
| 		let max = exclusiveMaximum ? value >= maximum : value > maximum; | ||||
|  | ||||
| 		if (minimum !== undefined && min) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ? | ||||
| 				'exclusiveMinimum' : 'minimum' | ||||
| 			]) | ||||
| 		} else if (maximum !== undefined && max) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ? | ||||
| 				'exclusiveMaximum' : 'maximum' | ||||
| 			]) | ||||
| 		} else if (minimum !== undefined && maximum !== undefined && (min || max)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['number'].range) | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	rangeLength(rule, value, message) { | ||||
| 		if (!types.string(value) && !types.array(value)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); | ||||
| 		} | ||||
|  | ||||
| 		let min = rule.minLength; | ||||
| 		let max = rule.maxLength; | ||||
| 		let val = value.length; | ||||
|  | ||||
| 		if (min !== undefined && val < min) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['length'].minLength) | ||||
| 		} else if (max !== undefined && val > max) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['length'].maxLength) | ||||
| 		} else if (min !== undefined && max !== undefined && (val < min || val > max)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message['length'].range) | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	pattern(rule, value, message) { | ||||
| 		if (!types['pattern'](rule.pattern, value)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	format(rule, value, message) { | ||||
| 		var customTypes = Object.keys(types); | ||||
| 		var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType); | ||||
|  | ||||
| 		if (customTypes.indexOf(format) > -1) { | ||||
| 			if (!types[format](value)) { | ||||
| 				return formatMessage(rule, rule.errorMessage || message.typeError); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	}, | ||||
|  | ||||
| 	arrayTypeFormat(rule, value, message) { | ||||
| 		if (!Array.isArray(value)) { | ||||
| 			return formatMessage(rule, rule.errorMessage || message.typeError); | ||||
| 		} | ||||
|  | ||||
| 		for (let i = 0; i < value.length; i++) { | ||||
| 			const element = value[i]; | ||||
| 			let formatResult = this.format(rule, element, message) | ||||
| 			if (formatResult !== null) { | ||||
| 				return formatResult | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return null | ||||
| 	} | ||||
| } | ||||
|  | ||||
| class SchemaValidator extends RuleValidator { | ||||
|  | ||||
| 	constructor(schema, options) { | ||||
| 		super(SchemaValidator.message); | ||||
|  | ||||
| 		this._schema = schema | ||||
| 		this._options = options || null | ||||
| 	} | ||||
|  | ||||
| 	updateSchema(schema) { | ||||
| 		this._schema = schema | ||||
| 	} | ||||
|  | ||||
| 	async validate(data, allData) { | ||||
| 		let result = this._checkFieldInSchema(data) | ||||
| 		if (!result) { | ||||
| 			result = await this.invokeValidate(data, false, allData) | ||||
| 		} | ||||
| 		return result.length ? result[0] : null | ||||
| 	} | ||||
|  | ||||
| 	async validateAll(data, allData) { | ||||
| 		let result = this._checkFieldInSchema(data) | ||||
| 		if (!result) { | ||||
| 			result = await this.invokeValidate(data, true, allData) | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	async validateUpdate(data, allData) { | ||||
| 		let result = this._checkFieldInSchema(data) | ||||
| 		if (!result) { | ||||
| 			result = await this.invokeValidateUpdate(data, false, allData) | ||||
| 		} | ||||
| 		return result.length ? result[0] : null | ||||
| 	} | ||||
|  | ||||
| 	async invokeValidate(data, all, allData) { | ||||
| 		let result = [] | ||||
| 		let schema = this._schema | ||||
| 		for (let key in schema) { | ||||
| 			let value = schema[key] | ||||
| 			let errorMessage = await this.validateRule(key, value, data[key], data, allData) | ||||
| 			if (errorMessage != null) { | ||||
| 				result.push({ | ||||
| 					key, | ||||
| 					errorMessage | ||||
| 				}) | ||||
| 				if (!all) break | ||||
| 			} | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	async invokeValidateUpdate(data, all, allData) { | ||||
| 		let result = [] | ||||
| 		for (let key in data) { | ||||
| 			let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData) | ||||
| 			if (errorMessage != null) { | ||||
| 				result.push({ | ||||
| 					key, | ||||
| 					errorMessage | ||||
| 				}) | ||||
| 				if (!all) break | ||||
| 			} | ||||
| 		} | ||||
| 		return result | ||||
| 	} | ||||
|  | ||||
| 	_checkFieldInSchema(data) { | ||||
| 		var keys = Object.keys(data) | ||||
| 		var keys2 = Object.keys(this._schema) | ||||
| 		if (new Set(keys.concat(keys2)).size === keys2.length) { | ||||
| 			return '' | ||||
| 		} | ||||
|  | ||||
| 		var noExistFields = keys.filter((key) => { | ||||
| 			return keys2.indexOf(key) < 0; | ||||
| 		}) | ||||
| 		var errorMessage = formatMessage({ | ||||
| 			field: JSON.stringify(noExistFields) | ||||
| 		}, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid']) | ||||
| 		return [{ | ||||
| 			key: 'invalid', | ||||
| 			errorMessage | ||||
| 		}] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| function Message() { | ||||
| 	return { | ||||
| 		TAG: "", | ||||
| 		default: '验证错误', | ||||
| 		defaultInvalid: '提交的字段{field}在数据库中并不存在', | ||||
| 		validateFunction: '验证无效', | ||||
| 		required: '{label}必填', | ||||
| 		'enum': '{label}超出范围', | ||||
| 		timestamp: '{label}格式无效', | ||||
| 		whitespace: '{label}不能为空', | ||||
| 		typeError: '{label}类型无效', | ||||
| 		date: { | ||||
| 			format: '{label}日期{value}格式无效', | ||||
| 			parse: '{label}日期无法解析,{value}无效', | ||||
| 			invalid: '{label}日期{value}无效' | ||||
| 		}, | ||||
| 		length: { | ||||
| 			minLength: '{label}长度不能少于{minLength}', | ||||
| 			maxLength: '{label}长度不能超过{maxLength}', | ||||
| 			range: '{label}必须介于{minLength}和{maxLength}之间' | ||||
| 		}, | ||||
| 		number: { | ||||
| 			minimum: '{label}不能小于{minimum}', | ||||
| 			maximum: '{label}不能大于{maximum}', | ||||
| 			exclusiveMinimum: '{label}不能小于等于{minimum}', | ||||
| 			exclusiveMaximum: '{label}不能大于等于{maximum}', | ||||
| 			range: '{label}必须介于{minimum}and{maximum}之间' | ||||
| 		}, | ||||
| 		pattern: { | ||||
| 			mismatch: '{label}格式不匹配' | ||||
| 		} | ||||
| 	}; | ||||
| } | ||||
|  | ||||
|  | ||||
| SchemaValidator.message = new Message(); | ||||
|  | ||||
| export default SchemaValidator | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV