feat: env 增加 replaceText 配置用于 schema 里的文本替换 (#2992)

* feat: env 增加 replaceText 配置用于 schema 里的文本替换

* 优化一下,改成黑名单模式;并且对 key 进行排序避免前缀匹配问题

* 修复编译报错
This commit is contained in:
吴多益 2021-11-22 12:41:26 +08:00 committed by GitHub
parent 82006c490e
commit f936777b64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 25 deletions

View File

@ -101,9 +101,42 @@ import 'amis/lib/locale/en-US';
}
```
## 全局文本替换
amis 渲染的 env 参数可以配置全局文本替换,能基于它实现多语言替换功能
```javascript
// sdk 中的写法
let amisScoped = amis.embed(
'#root',
amisJSON,
{
// 这是 props
},
{
replaceText: {
AMIS_HOST: 'https://baidu.gitee.io/amis'
}
}
);
```
比如下面的例子会对 `AMIS_HOST` 进行替换
```schema: scope="body"
{
"type": "tpl",
"tpl": "AMIS_HOST"
}
```
这个配置会对配置中的绝大部分字段进行替换,除了 `type, name, mode, target, reload` 这几个有特殊功能的字段。
可以通过配置 `replaceTextIgnoreKeys` 属性来进行修改,让其它字段也避免被替换。
## JSON 配置中设置多语言
在 JSON 配置中,也可以设置不同语言下的不同展现,比如前面设置了 `locale``en-US`,这时在任意 JSON 配置中都能使用 `en-US` 对象来覆盖这个语言下的效果。
在 JSON 配置中,也可以设置不同语言下的不同展现,比如前面设置了 `locale``en-US`,这时在任意 JSON 配置中都能使用 `en-US` 对象来覆盖这个语言下的效果,用于实现简单的替换效果
```schema: scope="body"
{

View File

@ -63,12 +63,9 @@ title: 常见问题
## 如何支持配置中的 URL 地址替换?
有个常用场景是在开发时使用 `localhost` 地址,而线上使用 `xxx.com`,这时有两个办法:
> 1.5.0 及以上版本
1. 自己做 JSON 替换,这样可以实现更灵活的替换
2. 通过外部 data 传入
这里介绍第二个方法,在渲染 amis 的时候,有第三个参数,可以传递 data这时就能增加一个域名变量比如
有个常用场景是在开发时使用 `localhost` 地址,而线上使用 `xxx.com`,这时可以使用 `replaceText` 属性,它是第四个参数,也就是 env 参数
```javascript
let amis = amisRequire('amis/embed');
@ -76,21 +73,24 @@ let amisJSON = {
type: 'page',
body: {
type: 'service',
api: '${HOST|raw}/api'
api: 'HOST/api'
}
};
let amisScoped = amis.embed('#root', amisJSON, {
data: {
HOST: 'http://localhost:3000'
let amisScoped = amis.embed(
'#root',
amisJSON,
{},
{
replaceText: {
HOST: 'http://localhost'
}
}
});
);
```
这样只需要修改 HOST 变量的值就能控制这个 URL 地址。
## 如何更新全局 data
使用下面的方式
使用下面的方式
```
amisScoped.updateProps({

View File

@ -714,3 +714,43 @@ render 有三个参数,后面会详细说明这三个参数内的属性
#### hideValidateFailedDetail: boolean
Form 表单验证失败时在 notify 消息提示中是否隐藏详细信息,默认展示,设置为 true 时隐藏
#### replaceText
> 1.5.0 及以上版本
可以用来实现变量替换及多语言功能,比如下面的例子
```javascript
let amisScoped = amis.embed(
'#root',
{
type: 'page',
body: {
type: 'service',
api: 'service/api'
}
},
{},
{
replaceText: {
service: 'http://localhost'
},
replaceTextKeys: ['api']
}
);
```
它会替换 `api` 里的 `service` 字符串
#### replaceTextIgnoreKeys
> 1.5.0 及以上版本
和前面的 `replaceText` 配合使用,某些字段会禁用文本替换,默认有以下:
```
type, name, mode, target, reload
```
如果发现有字段被意外替换了,可以通过设置这个属性来避免

View File

@ -223,6 +223,9 @@ export default class PlayGround extends React.Component {
},
tracker(eventTrack) {
console.debug('eventTrack', eventTrack);
},
replaceText: {
AMIS_HOST: 'https://baidu.gitee.io/amis'
}
};

View File

@ -5,6 +5,7 @@ import 'core-js/es/array/find-index';
import 'core-js/es/string/starts-with';
import 'core-js/es/string/ends-with';
import 'core-js/es/string/includes';
import 'core-js/es/string/replace-all';
import 'core-js/es/number/is-nan';
import 'core-js/es/promise';
import 'core-js/es/object/assign';

View File

@ -4,18 +4,31 @@
import {SchemaNode, Schema} from './types';
import {RendererProps, RendererConfig, addSchemaFilter} from './factory';
import {findObjectsWithKey} from './utils/helper';
const isMobile = (window as any).matchMedia?.('(max-width: 768px)').matches
? true
: false;
addSchemaFilter(function (schema: Schema, renderer, props?: any) {
if (schema && schema.mobile && isMobile) {
return {...schema, ...schema.mobile};
// 这里不能用 addSchemaFilter 是因为还需要更深层的替换,比如 select 里的 options
export const envOverwrite = (schema: any, locale?: string) => {
if (schema.mobile && isMobile) {
Object.assign(schema, schema.mobile);
delete schema.mobile;
}
if (props?.locale && schema[props.locale]) {
return {...schema, ...schema[props.locale]};
if (locale) {
let schemaNodes = findObjectsWithKey(schema, locale);
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode[locale]);
delete schemaNode[locale];
}
}
return schema;
});
if (isMobile) {
let schemaNodes = findObjectsWithKey(schema, 'mobile');
for (let schemaNode of schemaNodes) {
Object.assign(schemaNode, schemaNode['mobile']);
delete schemaNode['mobile'];
}
}
};

View File

@ -3,7 +3,14 @@ import {RendererStore, IRendererStore, IIRendererStore} from './store/index';
import {getEnv, destroy} from 'mobx-state-tree';
import {wrapFetcher} from './utils/api';
import {normalizeLink} from './utils/normalizeLink';
import {findIndex, promisify, qsparse, string2regExp} from './utils/helper';
import {
findIndex,
isObject,
JSONTraverse,
promisify,
qsparse,
string2regExp
} from './utils/helper';
import {
Api,
fetcherResult,
@ -25,6 +32,7 @@ import {getDefaultLocale, makeTranslator, LocaleProps} from './locale';
import ScopedRootRenderer, {RootRenderProps} from './Root';
import {HocStoreFactory} from './WithStore';
import {EnvContext, RendererEnv} from './env';
import {envOverwrite} from './envOverwrite';
export interface TestFunc {
(
@ -127,6 +135,14 @@ export interface RenderOptions {
affixOffsetTop?: number;
affixOffsetBottom?: number;
richTextToken?: string;
/**
* URL
*/
replaceText?: {[propName: string]: any};
/**
* fangs
*/
replaceTextIgnoreKeys?: String[];
[propName: string]: any;
}
@ -335,7 +351,15 @@ const defaultOptions: RenderOptions = {
},
// 用于跟踪用户在界面中的各种操作
tracker(eventTrack: EventTrack, props: PlainObject) {},
rendererResolver: resolveRenderer
rendererResolver: resolveRenderer,
replaceTextIgnoreKeys: [
'type',
'name',
'mode',
'target',
'reload',
'persistData'
]
};
let stores: {
[propName: string]: IRendererStore;
@ -355,6 +379,9 @@ export function render(
const translate = props.translate || makeTranslator(locale);
let store = stores[options.session || 'global'];
// 根据环境覆盖 schema这个要在最前面做不然就无法覆盖 validations
envOverwrite(schema, locale);
if (!store) {
options = {
...defaultOptions,
@ -387,6 +414,25 @@ export function render(
env.locale = locale;
}
// 进行文本替换
if (env.replaceText && isObject(env.replaceText)) {
const replaceKeys = Object.keys(env.replaceText);
replaceKeys.sort().reverse(); // 避免用户将短的放前面
const replaceTextIgnoreKeys = new Set(env.replaceTextIgnoreKeys || []);
JSONTraverse(schema, (value: any, key: string, object: any) => {
if (typeof value === 'string' && !replaceTextIgnoreKeys.has(key)) {
for (const replaceKey of replaceKeys) {
if (~value.indexOf(replaceKey)) {
object[key] = value.replaceAll(
replaceKey,
env.replaceText[replaceKey]
);
}
}
}
});
}
return (
<EnvContext.Provider value={env}>
<ScopedRootRenderer

View File

@ -1706,3 +1706,22 @@ export function isClickOnInput(e: React.MouseEvent<HTMLElement>) {
}
return false;
}
/**
* schema
* @param json
* @param mapper
*/
export function JSONTraverse(
json: any,
mapper: (value: any, key: string | number, host: Object) => any
) {
Object.keys(json).forEach(key => {
const value: any = json[key];
if (isPlainObject(value) || Array.isArray(value)) {
JSONTraverse(value, mapper);
} else {
mapper(value, key, json);
}
});
}

View File

@ -3,7 +3,7 @@
"outDir": "output/",
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom", "ES2015"],
"lib": ["es6", "dom", "ES2015", "es2021"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",

View File

@ -3,7 +3,7 @@
"outDir": "output/",
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom", "ES2015"],
"lib": ["es6", "dom", "ES2015", "es2021"],
"sourceMap": true,
"jsx": "react",
"moduleResolution": "node",