UNPKG

zgg-ui

Version:

2.0.1 正式版本 基于 Vue 3、UniApp 和 TypeScript 开发的多端适配移动端 UI 组件库,旨在提供丰富的组件集合,帮助开发者快速构建多端响应式的移动应用界面

551 lines (472 loc) 14.6 kB
type ValidationError = { message: string field: string fullField: string type: string } type Rule = { type?: string required?: boolean pattern?: RegExp message?: string asyncValidator?: ( rule: Rule, value: any, callback: (errors?: ValidationError[] | null) => void, source: any, options: any ) => Promise<void> | void validator?: ( rule: Rule, value: any, callback: (errors?: ValidationError[] | null) => void, source: any, options: any ) => boolean | ValidationError[] | Error | void | Promise<void> transform?: (value: any) => any fields?: Record<string, Rule | Rule[]> defaultField?: Rule fullField?: string [key: string]: any } interface MessageTypeObject { [key: string]: string | MessageTypeObject } type MessageType = MessageTypeObject function newMessages(): MessageType { return { default: 'Validation error on field %s', required: '%s is required', enum: '%s must be one of %s', whitespace: '%s cannot be empty', date: { format: '%s date %s is invalid for format %s', parse: '%s date could not be parsed, %s is invalid', invalid: '%s date %s is invalid', }, types: { string: '%s is not a %s', method: '%s is not a %s (function)', array: '%s is not an %s', object: '%s is not an %s', number: '%s is not a %s', date: '%s is not a %s', boolean: '%s is not a %s', integer: '%s is not an %s', float: '%s is not a %s', regexp: '%s is not a valid %s', email: '%s is not a valid %s', url: '%s is not a valid %s', hex: '%s is not a valid %s', }, string: { len: '%s must be exactly %s characters', min: '%s must be at least %s characters', max: '%s cannot be longer than %s characters', range: '%s must be between %s and %s characters', }, number: { len: '%s must equal %s', min: '%s cannot be less than %s', max: '%s cannot be greater than %s', range: '%s must be between %s and %s', }, array: { len: '%s must be exactly %s in length', min: '%s cannot be less than %s in length', max: '%s cannot be greater than %s in length', range: '%s must be between %s and %s in length', }, pattern: { mismatch: '%s value %s does not match pattern %s', }, // clone: function clone() { // const cloned = JSON.parse(JSON.stringify(this)) // cloned.clone = this.clone // return cloned // }, } } const messages = newMessages() interface SchemaOptions { messages?: MessageType keys?: string[] suppressWarning?: boolean first?: boolean error?: (rule: Rule, message: string) => ValidationError } type ValidationCallback = ( errors?: ValidationError[] | null, fields?: Record<string, ValidationError[]> ) => void interface ValidatorFunction { ( rule: Rule, value: any, callback: ValidationCallback, source: any, options: SchemaOptions ): void } type Validators = Record<string, ValidatorFunction> const validators: Validators = { // Add your validators here required: (rule, value, callback) => { if (value === undefined || value === null || value === '') { callback([ { message: rule.message || 'Field is required', field: rule.field!, fullField: rule.fullField!, type: rule.type!, }, ]) } else { callback() } }, string: (rule, value, callback) => { if (typeof value !== 'string') { callback([ { message: rule.message || 'Field must be a string', field: rule.field!, fullField: rule.fullField!, type: rule.type!, }, ]) } else { callback() } }, } function deepMerge(target: any, source: any) { for (const key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && source[key] !== null) { if (!target[key]) { target[key] = {} } deepMerge(target[key], source[key]) } else { target[key] = source[key] } } } return target } function format(message: string, ...args: any[]): string { return message.replace(/%s/g, () => args.shift()) } function complementError(rule: Rule): (error: ValidationError) => ValidationError { return (error: ValidationError) => { error.field = rule.fullField! error.type = rule.type! return error } } function convertFieldsError( errors: ValidationError[] ): Record<string, ValidationError[]> { const fields: Record<string, ValidationError[]> = {} errors.forEach(error => { const field = error.field fields[field] = fields[field] || [] fields[field].push(error) }) return fields } function asyncMap( series: Record<string, any[]>, options: SchemaOptions, iterator: (data: any, callback: (errors: ValidationError[] | null) => void) => void, callback: (results: ValidationError[][]) => void ): void { const results: ValidationError[][] = [] const keys = Object.keys(series) let remaining = keys.length if (!remaining) { callback(results) return } function done(errors: ValidationError[] | null) { results.push(errors || []) if (--remaining === 0) { callback(results) } } keys.forEach(key => { const tasks = series[key] tasks.forEach(task => { iterator(task, done) }) }) } class Schema { rules: Record<string, Rule[]> | null _messages: MessageType constructor(descriptor: Record<string, Rule | Rule[]>) { this.rules = null this._messages = messages this.define(descriptor) } messages(_messages?: MessageType): MessageType { if (_messages) { this._messages = deepMerge(newMessages(), _messages) } return this._messages } define(rules: Record<string, Rule | Rule[]>): void { if (!rules) { throw new Error('Cannot configure a schema with no rules') } if (typeof rules !== 'object' || Array.isArray(rules)) { throw new Error('Rules must be an object') } this.rules = {} for (const z in rules) { if (rules.hasOwnProperty(z)) { const item = rules[z] this.rules[z] = Array.isArray(item) ? item : [item] } } } validate( source_: Record<string, any>, o?: SchemaOptions, oc?: ValidationCallback ): Promise<void> { const _this = this let options = o || {} let callback = oc || function () {} if (typeof options === 'function') { callback = options options = {} } if (!this.rules || Object.keys(this.rules).length === 0) { if (callback) { callback() } return Promise.resolve() } function complete(results: ValidationError[][]) { let errors: ValidationError[] = [] let fields: Record<string, ValidationError[]> = {} function add(e: ValidationError | ValidationError[]) { if (Array.isArray(e)) { errors = errors.concat(e) } else { errors.push(e) } } results.forEach(result => add(result)) if (!errors.length) { errors = [] fields = {} } else { fields = convertFieldsError(errors) } callback(errors, fields) } if (options.messages) { let messages$1 = this.messages() if (messages$1 === messages) { messages$1 = newMessages() } deepMerge(messages$1, options.messages) options.messages = messages$1 } else { options.messages = this.messages() } const series: Record<string, any[]> = {} const keys = options.keys || Object.keys(this.rules) keys.forEach(z => { const arr = _this.rules![z] let value = source_[z] arr.forEach(r => { let rule = r if (typeof rule.transform === 'function') { value = source_[z] = rule.transform(value) } if (typeof rule === 'function') { rule = { validator: rule, } } else { rule = { ...rule } } rule.validator = _this.getValidationMethod(rule) rule.field = z rule.fullField = rule.fullField || z rule.type = _this.getType(rule) if (!rule.validator) { return } series[z] = series[z] || [] series[z].push({ rule, value, source: source_, field: z, }) }) }) const errorFields: Record<string, number> = {} return new Promise((resolve, reject) => { asyncMap( series, options, (data, doIt) => { const { rule } = data let deep = (rule.type === 'object' || rule.type === 'array') && (typeof rule.fields === 'object' || typeof rule.defaultField === 'object') deep = deep && (rule.required || (!rule.required && data.value)) rule.field = data.field function addFullfield(key: string, schema: Rule) { return { ...schema, fullField: `${rule.fullField}.${key}` } } function cb(e?: ValidationError[] | null) { if (e === void 0) { e = [] } let errors = e if (!Array.isArray(errors)) { errors = errors ? errors : [] } if (!options.suppressWarning && errors.length) { Schema.warning('async-validator:', errors) } if (errors.length && rule.message) { errors = [].concat(rule.message) } errors = errors.map(complementError(rule)) if (options.first && errors.length) { errorFields[rule.field] = 1 return doIt(errors) } if (!deep) { doIt(errors) } else { if (rule.required && !data.value) { if (rule.message) { errors = [].concat(rule.message).map(complementError(rule)) } else if (options.error) { errors = [ options.error( rule, format(options.messages?.required as string, rule.field) ), ] } else { errors = [] } return doIt(errors) } let fieldsSchema: Record<string, Rule> = {} if (rule.defaultField) { for (const k in data.value) { if (data.value.hasOwnProperty(k)) { fieldsSchema[k] = rule.defaultField } } } fieldsSchema = { ...fieldsSchema, ...data.rule.fields } for (const f in fieldsSchema) { if (fieldsSchema.hasOwnProperty(f)) { const fieldSchema = Array.isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]] fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f)) } } const schema = new Schema(fieldsSchema) schema.messages(options.messages) if (data.rule.options) { data.rule.options.messages = options.messages data.rule.options.error = options.error } schema.validate(data.value, data.rule.options || options, errs => { const finalErrors: ValidationError[] = [] if (errors && errors.length) { finalErrors.push.apply(finalErrors, errors) } if (errs && errs.length) { finalErrors.push.apply(finalErrors, errs) } doIt(finalErrors.length ? finalErrors : null) }) } } let res if (rule.asyncValidator) { res = rule.asyncValidator(rule, data.value, cb, data.source, options) } else if (rule.validator) { res = rule.validator(rule, data.value, cb, data.source, options) if (res === true) { cb() } else if (res === false) { cb(rule.message || `${rule.field} fails`) } else if (res instanceof Array) { cb(res) } else if (res instanceof Error) { cb(res.message as any) } } if (res && res.then) { res.then( () => cb(null), (e: ValidationError) => cb([e]) ) } }, results => { complete(results) resolve() } ) }) } getType(rule: Rule): string { if (rule.type === undefined && rule.pattern instanceof RegExp) { rule.type = 'pattern' } if ( typeof rule.validator !== 'function' && rule.type && !validators.hasOwnProperty(rule.type) ) { throw new Error(format('Unknown rule type %s', rule.type)) } return rule.type || 'string' } getValidationMethod(rule: Rule): ValidatorFunction { if (typeof rule.validator === 'function') { return rule.validator } const keys = Object.keys(rule) const messageIndex = keys.indexOf('message') if (messageIndex !== -1) { keys.splice(messageIndex, 1) } if (keys.length === 1 && keys[0] === 'required') { return validators.required } return validators[this.getType(rule)] || false } static register(type: string, validator: ValidatorFunction): void { if (typeof validator !== 'function') { throw new Error( 'Cannot register a validator by type, validator is not a function' ) } validators[type] = validator } static warning(...args: any[]): void { console.warn(...args) } static messages(): MessageType { return messages } } export default Schema