From 738b751186376848d57a6c5e4079f348d7274b27 Mon Sep 17 00:00:00 2001 From: 2betop <2betop.cn@gmail.com> Date: Wed, 3 Jun 2020 14:51:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=98=E6=9C=89=E9=83=A8=E5=88=86=20form=20?= =?UTF-8?q?=E7=9A=84=E6=96=87=E5=AD=97=E6=B2=A1=E6=8F=90=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Toast.tsx | 26 ++++++---- src/factory.tsx | 10 ++-- src/locale/en.ts | 50 +++++++++++++++++++- src/store/crud.ts | 10 ++-- src/store/form.ts | 8 ++-- src/store/formItem.ts | 28 ++++++++--- src/store/iRenderer.ts | 12 ++++- src/store/index.ts | 5 ++ src/store/service.ts | 8 +++- src/utils/validations.ts | 100 ++++++++++++++++++++------------------- 10 files changed, 180 insertions(+), 77 deletions(-) diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index 0eaa362b..d2a447cd 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -14,8 +14,9 @@ import React from 'react'; import cx from 'classnames'; import Html from './Html'; import {uuid, autobind, noop} from '../utils/helper'; -import {ClassNamesFn, themeable, classnames} from '../theme'; +import {ClassNamesFn, themeable, classnames, ThemeProps} from '../theme'; import {Icon} from './icons'; +import {LocaleProps, localeable, TranslateFn} from '../locale'; interface Config { closeButton?: boolean; @@ -43,7 +44,7 @@ const show = ( toastRef[method](content, title || '', {...conf}); }; -interface ToastComponentProps { +interface ToastComponentProps extends ThemeProps, LocaleProps { position: | 'top-right' | 'top-center' @@ -53,8 +54,6 @@ interface ToastComponentProps { | 'bottom-right'; closeButton: boolean; timeout: number; - classPrefix: string; - classnames: ClassNamesFn; className?: string; } @@ -146,7 +145,13 @@ export class ToastComponent extends React.Component< return null; } - const {classnames: cx, className, timeout, position} = this.props; + const { + classnames: cx, + className, + timeout, + position, + translate + } = this.props; const items = this.state.items; return ( @@ -168,6 +173,7 @@ export class ToastComponent extends React.Component< timeout={item.timeout ?? timeout} closeButton={item.closeButton} onDismiss={this.handleDismissed.bind(this, index)} + translate={translate} /> ))} @@ -175,7 +181,7 @@ export class ToastComponent extends React.Component< } } -export default themeable(ToastComponent); +export default themeable(localeable(ToastComponent)); interface ToastMessageProps { title?: string; @@ -192,6 +198,7 @@ interface ToastMessageProps { | 'bottom-right'; onDismiss?: () => void; classnames: ClassNamesFn; + translate: TranslateFn; allowHtml: boolean; } @@ -265,7 +272,8 @@ export class ToastMessage extends React.Component< title, body, allowHtml, - level + level, + translate: __ } = this.props; return ( @@ -290,7 +298,9 @@ export class ToastMessage extends React.Component< ) : null} - {title ?
{title}
: null} + {title ? ( +
{__(title)}
+ ) : null}
{allowHtml ? : body}
diff --git a/src/factory.tsx b/src/factory.tsx index 51b5b674..58b0374c 100644 --- a/src/factory.tsx +++ b/src/factory.tsx @@ -956,6 +956,8 @@ export function render( ...options }; + const locale = props.locale || getDefaultLocale(); + const translate = props.translate || makeTranslator(locale); let store = stores[options.session || 'global'] || (stores[options.session || 'global'] = RendererStore.create( @@ -967,7 +969,9 @@ export function render( : defaultOptions.fetcher, confirm: options.confirm ? promisify(options.confirm) - : defaultOptions.confirm + : defaultOptions.confirm, + locale, + translate } )); @@ -975,8 +979,8 @@ export function render( const env = getEnv(store); const theme = props.theme || options.theme || 'default'; env.theme = getTheme(theme); - const locale = props.locale || getDefaultLocale(); - const translate = props.translate || makeTranslator(locale); + env.translate = translate; + env.locale = locale; return ( str; } }; }) @@ -303,7 +313,13 @@ export const FormItemStore = types } addError( - doValidate(self.value, self.form.data, self.rules, self.messages) + doValidate( + self.value, + self.form.data, + self.rules, + self.messages, + self.__ + ) ); self.validated = true; @@ -320,7 +336,7 @@ export const FormItemStore = types item => item !== self && self.value && item.value === self.value ) ) { - addError(`当前值不唯一`); + addError(self.__('`当前值不唯一`')); } } @@ -398,9 +414,9 @@ export const FormItemStore = types if (!json.ok) { setErrorFlag !== false && setError( - `加载选项失败,原因:${ - json.msg || (config && config.errorMessage) - }` + self.__('加载选项失败,原因:{{reason}}', { + reason: json.msg || (config && config.errorMessage) + }) ); (getRoot(self) as IRendererStore).notify( 'error', diff --git a/src/store/iRenderer.ts b/src/store/iRenderer.ts index b0796de6..bbfd03fd 100644 --- a/src/store/iRenderer.ts +++ b/src/store/iRenderer.ts @@ -3,6 +3,7 @@ import {extendObject, createObject} from '../utils/helper'; import {IRendererStore} from './index'; import {dataMapping} from '../utils/tpl-builtin'; import {SimpleMap} from '../utils/SimpleMap'; +import {TranslateFn} from '../locale'; export const iRendererStore = types .model('iRendererStore', { @@ -25,7 +26,6 @@ export const iRendererStore = types }) .views(self => { return { - // todo 不能自己引用自己 get parentStore(): any { return isAlive(self) && self.parentId && @@ -33,6 +33,14 @@ export const iRendererStore = types (getRoot(self) as IRendererStore).storeType === 'RendererStore' ? (getRoot(self) as IRendererStore).stores.get(self.parentId) : null; + }, + + get __(): TranslateFn { + return isAlive(self) && + getRoot(self) && + (getRoot(self) as IRendererStore).storeType === 'RendererStore' + ? (getRoot(self) as IRendererStore).__ + : (str: string) => str; } }; }) @@ -62,7 +70,7 @@ export const iRendererStore = types self.data = self.pristine; }, - updateData(data: object = {}, tag?: object, replace?:boolean) { + updateData(data: object = {}, tag?: object, replace?: boolean) { const prev = self.data; let newData; if (tag) { diff --git a/src/store/index.ts b/src/store/index.ts index 957e2d2e..2b10d285 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -8,6 +8,7 @@ import {CRUDStore} from './crud'; import {TableStore} from './table'; import {ListStore} from './list'; import {ModalStore} from './modal'; +import {TranslateFn} from '../locale'; setLivelynessChecking( process.env.NODE_ENV === 'production' ? 'ignore' : 'error' @@ -56,6 +57,10 @@ export const RendererStore = types get isCancel(): (value: any) => boolean { return getEnv(self).isCancel; + }, + + get __(): TranslateFn { + return getEnv(self).translate; } })) .views(self => ({ diff --git a/src/store/service.ts b/src/store/service.ts index 3169e71d..9e03815e 100644 --- a/src/store/service.ts +++ b/src/store/service.ts @@ -278,7 +278,9 @@ export const ServiceStore = iRendererStore if (!json.ok) { updateMessage( - json.msg || (options && options.errorMessage) || '保存失败', + json.msg || + (options && options.errorMessage) || + self.__('保存失败'), true ); throw new ServerError(self.msg, json); @@ -370,7 +372,9 @@ export const ServiceStore = iRendererStore if (!json.ok) { updateMessage( - json.msg || (options && options.errorMessage) || '获取失败,请重试', + json.msg || + (options && options.errorMessage) || + self.__('获取失败,请重试'), true ); (getRoot(self) as IRendererStore).notify( diff --git a/src/utils/validations.ts b/src/utils/validations.ts index a2e8bd47..afc6ecb5 100644 --- a/src/utils/validations.ts +++ b/src/utils/validations.ts @@ -26,7 +26,7 @@ export interface ValidateFn { export const validations: { [propsName: string]: ValidateFn; } = { - isRequired: function(values, value: any) { + isRequired: function (values, value: any) { return ( value !== undefined && value !== '' && @@ -34,113 +34,113 @@ export const validations: { (!Array.isArray(value) || !!value.length) ); }, - isExisty: function(values, value) { + isExisty: function (values, value) { return isExisty(value); }, - matchRegexp: function(values, value, regexp) { + matchRegexp: function (values, value, regexp) { return !isExisty(value) || isEmpty(value) || makeRegexp(regexp).test(value); }, - isUndefined: function(values, value) { + isUndefined: function (values, value) { return value === undefined; }, - isEmptyString: function(values, value) { + isEmptyString: function (values, value) { return isEmpty(value); }, - isEmail: function(values, value) { + isEmail: function (values, value) { return validations.matchRegexp( values, value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i ); }, - isUrl: function(values, value) { + isUrl: function (values, value) { return validations.matchRegexp( values, value, /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i ); }, - isTrue: function(values, value) { + isTrue: function (values, value) { return value === true; }, - isFalse: function(values, value) { + isFalse: function (values, value) { return value === false; }, - isNumeric: function(values, value) { + isNumeric: function (values, value) { if (typeof value === 'number') { return true; } return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/); }, - isAlpha: function(values, value) { + isAlpha: function (values, value) { return validations.matchRegexp(values, value, /^[A-Z]+$/i); }, - isAlphanumeric: function(values, value) { + isAlphanumeric: function (values, value) { return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i); }, - isInt: function(values, value) { + isInt: function (values, value) { return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/); }, - isFloat: function(values, value) { + isFloat: function (values, value) { return validations.matchRegexp( values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/ ); }, - isWords: function(values, value) { + isWords: function (values, value) { return validations.matchRegexp(values, value, /^[A-Z\s]+$/i); }, - isSpecialWords: function(values, value) { + isSpecialWords: function (values, value) { return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i); }, - isLength: function(values, value, length) { + isLength: function (values, value, length) { return !isExisty(value) || isEmpty(value) || value.length === length; }, - equals: function(values, value, eql) { + equals: function (values, value, eql) { return !isExisty(value) || isEmpty(value) || value == eql; }, - equalsField: function(values, value, field) { + equalsField: function (values, value, field) { return value == values[field]; }, - maxLength: function(values, value, length) { + maxLength: function (values, value, length) { return !isExisty(value) || value.length <= length; }, - minLength: function(values, value, length) { + minLength: function (values, value, length) { return !isExisty(value) || isEmpty(value) || value.length >= length; }, - isUrlPath: function(values, value, regexp) { + isUrlPath: function (values, value, regexp) { return !isExisty(value) || isEmpty(value) || /^[a-z0-9_\\-]+$/i.test(value); }, - maximum: function(values, value, maximum) { + maximum: function (values, value, maximum) { return ( !isExisty(value) || isEmpty(value) || (parseFloat(value) || 0) <= (parseFloat(maximum) || 0) ); }, - lt: function(values, value, maximum) { + lt: function (values, value, maximum) { return ( !isExisty(value) || isEmpty(value) || (parseFloat(value) || 0) < (parseFloat(maximum) || 0) ); }, - minimum: function(values, value, minimum) { + minimum: function (values, value, minimum) { return ( !isExisty(value) || isEmpty(value) || (parseFloat(value) || 0) >= (parseFloat(minimum) || 0) ); }, - gt: function(values, value, minimum) { + gt: function (values, value, minimum) { return ( !isExisty(value) || isEmpty(value) || (parseFloat(value) || 0) > (parseFloat(minimum) || 0) ); }, - isJson: function(values, value, minimum) { + isJson: function (values, value, minimum) { if (isExisty(value) && !isEmpty(value)) { try { JSON.parse(value); @@ -150,24 +150,24 @@ export const validations: { } return true; }, - isPhoneNumber: function(values, value) { + isPhoneNumber: function (values, value) { return ( !isExisty(value) || isEmpty(value) || /^[1]([3-9])[0-9]{9}$/.test(value) ); }, - isTelNumber: function(values, value) { + isTelNumber: function (values, value) { return ( !isExisty(value) || isEmpty(value) || /^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$/.test(value) ); }, - isZipcode: function(values, value) { + isZipcode: function (values, value) { return ( !isExisty(value) || isEmpty(value) || /^[1-9]{1}(\d+){5}$/.test(value) ); }, - isId: function(values, value) { + isId: function (values, value) { return ( !isExisty(value) || isEmpty(value) || @@ -176,34 +176,34 @@ export const validations: { ) ); }, - notEmptyString: function(values, value) { + notEmptyString: function (values, value) { return !isExisty(value) || !(String(value) && String(value).trim() === ''); }, - matchRegexp1: function(values, value, regexp) { + matchRegexp1: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp2: function(values, value, regexp) { + matchRegexp2: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp3: function(values, value, regexp) { + matchRegexp3: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp4: function(values, value, regexp) { + matchRegexp4: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp5: function(values, value, regexp) { + matchRegexp5: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp6: function(values, value, regexp) { + matchRegexp6: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp7: function(values, value, regexp) { + matchRegexp7: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp8: function(values, value, regexp) { + matchRegexp8: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); }, - matchRegexp9: function(values, value, regexp) { + matchRegexp9: function (values, value, regexp) { return validations.matchRegexp(values, value, regexp); } }; @@ -252,7 +252,8 @@ export function validate( value: any, values: {[propName: string]: any}, rules: {[propName: string]: any}, - messages?: {[propName: string]: string} + messages?: {[propName: string]: string}, + __ = (str: string) => str ): Array { const errors: Array = []; @@ -275,9 +276,12 @@ export function validate( ) ) { errors.push( - filter((messages && messages[ruleName]) || validateMessages[ruleName], { - ...[''].concat(rules[ruleName]) - }) + filter( + __((messages && messages[ruleName]) || validateMessages[ruleName]), + { + ...[''].concat(rules[ruleName]) + } + ) ); } }); @@ -285,7 +289,7 @@ export function validate( return errors; } -const splitValidations = function(str: string): Array { +const splitValidations = function (str: string): Array { let i = 0; const placeholder: {[propName: string]: string} = {}; @@ -303,7 +307,7 @@ export function str2rules( ): {[propName: string]: any} { if (typeof validations === 'string') { return validations - ? splitValidations(validations).reduce(function( + ? splitValidations(validations).reduce(function ( validations: {[propName: string]: any}, validation ) { @@ -318,7 +322,7 @@ export function str2rules( : validation .substring(idx + 1) .split(',') - .map(function(arg) { + .map(function (arg) { try { return JSON.parse(arg); } catch (e) {