import * as React from 'react'; import {BasicConfig, BasicContainer, BasicContainerPropsInterface} from '../../render/core/Container/types'; import componentLoader from '../../render/util/componentLoader'; import {Form} from '@native-ads/antd'; import {createChild} from '../../render/util/createChild'; import {ColProps} from '@native-ads/antd/lib/grid/col'; import {applyRule} from './validate'; import {SET_FORM_ITEM_PAYLOAD} from '../../render/core/Form/actions'; import {compileExpressionString, isExpression, parseExpressionString} from '../../render/util/vm'; import {request} from '../../render/services/api'; export interface ValidateRules { /** * 字段长度 */ len?: number; /** * 最大值 */ max?: number; /** * 最大长度 */ maxLength?: number; /** * 最小值 */ min?: number; /** * 最小长度 */ minLength?: number; /** * 校验文案 */ message?: string; /** * 正则表达式 */ pattern?: string; /** * 是否必须 */ required?: boolean; /** * 必须时,空格是否会被认为是错误 */ whitespace?: boolean; } type ApiRule = { /** * 地址 */ url: string; /** * 请求方法 */ method: string; /** * 请求的数据 */ data: string; /** * 使用ExpressionString, 验证接口返回值是否合格 */ validate: string; /** * 错误信息 */ errmsg: string; }; class FormItemConfig extends BasicConfig { /** * 表单文字 */ label?: string; /** * label 标签布局 */ labelCol?: ColProps; /** * 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol */ wrapperCol?: ColProps; /** * 使用内置的验证规则 */ rules?: ValidateRules[]; /** * 使用ExpressionString来验证 */ filterRule?: string; /** * 使用ExpressionString验证失败的错误信息 */ filterErrMsg?: string; /** * 使用API来验证输入 */ apiRule?: ApiRule; /** * 显示反馈 */ feedback?: boolean; /** * 是否必须 */ required?: boolean; /** * 是否有反馈提示 */ hasFeedBack?: boolean; /** * 控制的表单元素 */ control: BasicConfig; /** * 额外的说明文字 */ extra?: string; } class FormItemPropsInterface extends BasicContainerPropsInterface { info: FormItemConfig; /** * 更新表单元素的属性 */ $setFormItem: (payload: Partial) => void; /** * 删除表单元素的验证信息 */ $deleteFormItem: (formItemName: string) => void; } class AbstractFormItem extends BasicContainer { private infoBlackList: string[]; private isApiValidate: boolean; constructor(props: FormItemPropsInterface) { super(props); this.handleChange = this.handleChange.bind(this); this.handleDelete = this.handleDelete.bind(this); this.handleBlur = this.handleBlur.bind(this); this.eventHandle = this.eventHandle.bind(this); this.validateFormItem = this.validateFormItem.bind(this); this.apiValidate = this.apiValidate.bind(this); this.infoBlackList = ['filterRule', 'filterErrMsg']; this.isApiValidate = false; } componentWillMount() { let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList); let isHidden = info.hidden === true || info.show === false; if (info.control.name && this.props.$setFormItem && !isHidden) { let formItemName = info.control.name; let valid = !info.required; if (valid && info.rules) { valid = info.rules.some(rule => !rule.required); } this.props.$setFormItem({ formItemName: formItemName, rules: info.rules, required: info.required, valid: valid }); } } componentWillReceiveProps(nextProps: FormItemPropsInterface) { let info = this.getPropsInfo(nextProps.info, nextProps, this.infoBlackList); let name = info.control.name; if (nextProps.$data && name) { let nextValue = this.getValueFromDataStore(name, nextProps.$data); if (nextValue) { this.validateFormItem(name, nextValue); } if (nextProps.$form && nextProps.$form.control[name]) { let itemInfo = nextProps.$form.control[name]; let shouldValidate = itemInfo.$validate; if (shouldValidate) { this.validateFormItem(name, nextValue); this.props.$setFormItem({ formItemName: name, $validate: false }); } } } } shouldComponentUpdate() { return true; } private async apiValidate(apiRule: ApiRule, formItemName: string, value: any) { let runTime = this.getRuntimeContext(); if (!apiRule.url) { console.error('至少提供一个请求的地址'); return; } if (!apiRule.validate || !isExpression(apiRule.validate)) { console.error('请提供验证所需的ExpressionString'); return; } let data; if (typeof apiRule.data === 'string') { data = parseExpressionString(apiRule.data, { ...runTime, $args: { value: value } }); } else { data = compileExpressionString(apiRule.data, { ...runTime, $args: { value: value } }); } let ret = await request(apiRule.url, { url: apiRule.url, method: apiRule.method, data: data }, runTime.$global.proxy); let isValid = parseExpressionString(apiRule.validate, { ...runTime, $args: { value: value }, $output: ret.data }); if (isValid) { this.props.$setFormItem({ formItemName: formItemName, valid: true, status: 'success' }); } else { let errmsg = apiRule.errmsg; if (isExpression(errmsg)) { errmsg = parseExpressionString(errmsg, { ...runTime, $args: { value: value }, $output: ret.data }); } this.props.$setFormItem({ formItemName: formItemName, valid: false, errorMsg: errmsg, status: 'error' }); } this.forceUpdate(); this.isApiValidate = false; } private validateFormItem(formItemName: string, data: any) { let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList); let rules = info.rules || []; let required = info.required; let filterRule = info.filterRule; let apiRule = info.apiRule; let runTime = this.getRuntimeContext(); let isValid = true; let errmsg = ''; if (required) { rules.push({ required: true }); } if (apiRule) { if (!this.isApiValidate) { this.apiValidate(apiRule, formItemName, data); this.isApiValidate = true; this.props.$setFormItem({ formItemName: formItemName, valid: true, status: 'validating' }); } return; } if (typeof filterRule === 'string' && isExpression(filterRule)) { isValid = parseExpressionString(filterRule, { ...runTime, $args: { value: data } }); let filterErrMsg = info.filterErrMsg || ''; if (typeof filterErrMsg === 'string' && isExpression(filterErrMsg)) { filterErrMsg = parseExpressionString(filterErrMsg, { ...runTime, $args: { value: data } }); } errmsg = filterErrMsg; } else if (rules.length > 0) { let result = { message: '', valid: false }; isValid = rules.every(rule => { result = applyRule(rule, data); return result.valid; }); if (!isValid) { errmsg = result.message; } } if (isValid) { this.props.$setFormItem({ formItemName: formItemName, valid: true, status: 'success' }); } else { this.props.$setFormItem({ formItemName: formItemName, valid: false, status: 'error', errorMsg: errmsg }); } } private handleChange(name: string, data: any) { // 输入值改变,需要重置API请求锁 this.isApiValidate = false; this.validateFormItem(name, data); if (this.props.$setData) { this.props.$setData(name, data); } } private handleDelete(name: string) { this.props.$deleteFormItem(name); if (this.props.$deleteData) { this.props.$deleteData(name); } } private handleBlur(args: Object) { let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList); let formItemName = info.control.name; if (formItemName) { let data = this.getValueFromDataStore(formItemName); this.validateFormItem(formItemName, data); this.forceUpdate(); } } private eventHandle(eventName: string, args: Object) { switch (eventName) { case 'onBlur': this.handleBlur(args); break; default: break; } } componentDidUpdate() { let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList); if (this.props.$form && info.control.name) { let itemState = this.props.$form.control[info.control.name]; if ((info.hidden === true || info.show === false) && itemState) { this.props.$deleteFormItem(info.control.name); } } } render() { let info = this.getPropsInfo(this.props.info, this.props, this.infoBlackList); if (!this.props.$form) { console.error('FormItem 组件必须在Form组件内部'); return
; } if (!info.control) { return this.errorReport('control property is required for FormGroup', 'div'); } let control = info.control; let formItemChildren; let formItemName = info.control.name; let validateStatus: 'success' | 'warning' | 'error' | 'validating' | undefined; let validateErrMsg: string | undefined; let required = info.required || (info.rules && info.rules.some(rule => !!rule.required)); if (formItemName && this.props.$form.control && this.props.$form.control[formItemName]) { let itemInfo = this.props.$form.control[formItemName]; validateStatus = itemInfo.status; if (!itemInfo.valid) { validateErrMsg = itemInfo.errorMsg; } } if (control instanceof Array) { formItemChildren = control.map((child, index) => createChild(child, { ...this.props, info: child, key: `form_item_child_${child.name}`, $setData: this.handleChange, $deleteData: this.handleDelete, eventHandle: this.eventHandle })); } else { formItemChildren = createChild(control, { ...this.props, info: control, $setData: this.handleChange, $deleteData: this.handleDelete, eventHandle: this.eventHandle }); } let labelCol: undefined | ColProps; let wrapperCol: undefined | ColProps; if (this.props.$form.layout === 'horizontal') { labelCol = info.labelCol || {span: 4}; wrapperCol = info.wrapperCol || {span: 24 - labelCol.span!}; } let hasFeedBack: boolean = true; if (info.hasFeedBack === false) { hasFeedBack = false; } const childElement = ( {formItemChildren} ); return this.renderChildren(info, childElement); } } componentLoader.addComponent('formItem', AbstractFormItem, FormItemPropsInterface);