mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
mapping 添加 source 接口 (#1752)
This commit is contained in:
parent
2ec81de736
commit
1dd386c089
@ -119,11 +119,65 @@ List 的内容、Card 卡片的内容配置同上
|
||||
}
|
||||
```
|
||||
|
||||
### 远程拉取字典
|
||||
|
||||
> since 1.1.6
|
||||
|
||||
通过配置 `source` 接口来实现,接口返回字典对象即可,数据格式参考 map 配置。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"data": {
|
||||
"type": "2"
|
||||
},
|
||||
"controls": [
|
||||
{
|
||||
"type": "mapping",
|
||||
"name": "type",
|
||||
"label": "映射",
|
||||
"source": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mapping"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 默认 source 是有 30s 缓存的,通常字典数据不长变更。如果想修改,请参考 [API](../../../docs/types/api) 文档配置缓存。
|
||||
|
||||
### 关联上下文变量
|
||||
|
||||
> since 1.1.6
|
||||
|
||||
同样通过配置 `source` 来实现,只是格式是取变量。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"initApi": {
|
||||
"url": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mapping",
|
||||
"method": "get",
|
||||
"responseData": {
|
||||
"zidian": "$$$$",
|
||||
"type": "2"
|
||||
}
|
||||
},
|
||||
"controls": [
|
||||
{
|
||||
"type": "mapping",
|
||||
"name": "type",
|
||||
"label": "映射",
|
||||
"source": "$${zidian}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | -------- | ------ | -------------------------------------------------------------------------------------- |
|
||||
| type | `string` | | 如果在 Table、Card 和 List 中,为`"color"`;在 Form 中用作静态展示,为`"static-color"` |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| placeholder | `string` | | 占位文本 |
|
||||
| map | `object` | | 映射配置 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | ----------------- | ------ | -------------------------------------------------------------------------------------- |
|
||||
| type | `string` | | 如果在 Table、Card 和 List 中,为`"color"`;在 Form 中用作静态展示,为`"static-color"` |
|
||||
| className | `string` | | 外层 CSS 类名 |
|
||||
| placeholder | `string` | | 占位文本 |
|
||||
| map | `object` | | 映射配置 |
|
||||
| source | `string` or `API` | | [API](../../../docs/types/api) 或 [数据映射](../../../docs/concepts/data-mapping) |
|
||||
|
11
mock/mapping.json
Normal file
11
mock/mapping.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"status": 0,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"1": "<span class='label label-info'>漂亮</span>",
|
||||
"2": "<span class='label label-success'>开心</span>",
|
||||
"3": "<span class='label label-danger'>惊吓</span>",
|
||||
"4": "<span class='label label-warning'>紧张</span>",
|
||||
"*": "其他:${type}"
|
||||
}
|
||||
}
|
@ -1,10 +1,27 @@
|
||||
import React from 'react';
|
||||
import {Renderer, RendererProps} from '../factory';
|
||||
import {Renderer, RendererEnv, RendererProps} from '../factory';
|
||||
import {ServiceStore, IServiceStore} from '../store/service';
|
||||
import {Api, SchemaNode, PlainObject} from '../types';
|
||||
import {Api, SchemaNode, PlainObject, Payload} from '../types';
|
||||
import {filter} from '../utils/tpl';
|
||||
import cx from 'classnames';
|
||||
import {BaseSchema, SchemaTpl} from '../Schema';
|
||||
import {
|
||||
BaseSchema,
|
||||
SchemaApi,
|
||||
SchemaTokenizeableString,
|
||||
SchemaTpl
|
||||
} from '../Schema';
|
||||
import {withStore} from '../components/WithStore';
|
||||
import {flow, Instance, types} from 'mobx-state-tree';
|
||||
import {getVariable, guid, isObject} from '../utils/helper';
|
||||
import {StoreNode} from '../store/node';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import {isPureVariable, resolveVariableAndFilter} from '../utils/tpl-builtin';
|
||||
import {
|
||||
buildApi,
|
||||
isApiOutdated,
|
||||
isEffectiveApi,
|
||||
normalizeApi
|
||||
} from '../utils/api';
|
||||
|
||||
/**
|
||||
* Mapping 映射展示控件。
|
||||
@ -28,54 +45,182 @@ export interface MappingSchema extends BaseSchema {
|
||||
[propName: string]: SchemaTpl;
|
||||
};
|
||||
|
||||
/**
|
||||
* 如果想远程拉取字典,请配置 source 为接口。
|
||||
*/
|
||||
source?: SchemaApi | SchemaTokenizeableString;
|
||||
|
||||
/**
|
||||
* 占位符
|
||||
*/
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface MappingProps
|
||||
extends RendererProps,
|
||||
Omit<MappingSchema, 'type' | 'className'> {}
|
||||
export const Store = StoreNode.named('MappingStore')
|
||||
.props({
|
||||
fetching: false,
|
||||
errorMsg: '',
|
||||
map: types.frozen<{
|
||||
[propName: string]: any;
|
||||
}>({})
|
||||
})
|
||||
.actions(self => {
|
||||
const load: (env: RendererEnv, api: Api, data: any) => Promise<any> = flow(
|
||||
function* (env, api, data) {
|
||||
try {
|
||||
self.fetching = true;
|
||||
const ret: Payload = yield env.fetcher(api, data);
|
||||
|
||||
export class MappingField extends React.Component<MappingProps, object> {
|
||||
static defaultProps: Partial<MappingProps> = {
|
||||
placeholder: '-',
|
||||
map: {
|
||||
'*': '通配值'
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {className, placeholder, map, render, classnames: cx} = this.props;
|
||||
let key = this.props.value;
|
||||
|
||||
let viewValue: React.ReactNode = (
|
||||
<span className="text-muted">{placeholder}</span>
|
||||
if (ret.ok) {
|
||||
const data = ret.data || {};
|
||||
(self as any).setMap(data);
|
||||
} else {
|
||||
throw new Error(ret.msg || 'fetch error');
|
||||
}
|
||||
} catch (e) {
|
||||
self.errorMsg = e.message;
|
||||
} finally {
|
||||
self.fetching = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
key =
|
||||
typeof key === 'string'
|
||||
? key.trim()
|
||||
: key === true
|
||||
? '1'
|
||||
: key === false
|
||||
? '0'
|
||||
: key; // trim 一下,干掉一些空白字符。
|
||||
return {
|
||||
load,
|
||||
setMap(options: any) {
|
||||
if (isObject(options)) {
|
||||
self.map = {
|
||||
...options
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (typeof key !== 'undefined' && map && (map[key] ?? map['*'])) {
|
||||
viewValue = render(
|
||||
'tpl',
|
||||
map[key] ?? map['*'] // 兼容平台旧用法:即 value 为 true 时映射 1 ,为 false 时映射 0
|
||||
);
|
||||
export type IStore = Instance<typeof Store>;
|
||||
|
||||
export interface MappingProps
|
||||
extends Omit<RendererProps, 'store'>,
|
||||
Omit<MappingSchema, 'type' | 'className'> {
|
||||
store: IStore;
|
||||
}
|
||||
|
||||
export const MappingField = withStore(props =>
|
||||
Store.create(
|
||||
{
|
||||
id: guid(),
|
||||
storeType: Store.name
|
||||
},
|
||||
props.env
|
||||
)
|
||||
)(
|
||||
class extends React.Component<MappingProps, object> {
|
||||
static defaultProps: Partial<MappingProps> = {
|
||||
placeholder: '-',
|
||||
map: {
|
||||
'*': '通配值'
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props: MappingProps) {
|
||||
super(props);
|
||||
|
||||
props.store.syncProps(props, undefined, ['map']);
|
||||
}
|
||||
|
||||
return <span className={cx('MappingField', className)}>{viewValue}</span>;
|
||||
componentDidMount() {
|
||||
const {store, source, data} = this.props;
|
||||
|
||||
this.reload();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: MappingProps) {
|
||||
const props = this.props;
|
||||
const {store, source, data} = this.props;
|
||||
|
||||
store.syncProps(props, prevProps, ['map']);
|
||||
|
||||
if (isPureVariable(source)) {
|
||||
const prev = resolveVariableAndFilter(
|
||||
prevProps.source as string,
|
||||
prevProps.data,
|
||||
'| raw'
|
||||
);
|
||||
const curr = resolveVariableAndFilter(source as string, data, '| raw');
|
||||
|
||||
if (prev !== curr) {
|
||||
store.setMap(curr);
|
||||
}
|
||||
} else if (
|
||||
isApiOutdated(
|
||||
prevProps.source,
|
||||
props.source,
|
||||
prevProps.data,
|
||||
props.data
|
||||
)
|
||||
) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
reload() {
|
||||
const {source, data, env} = this.props;
|
||||
const store = this.props.store;
|
||||
if (isPureVariable(source)) {
|
||||
store.setMap(resolveVariableAndFilter(source, data, '| raw'));
|
||||
} else if (isEffectiveApi(source, data)) {
|
||||
const api = normalizeApi(source, 'get');
|
||||
api.cache = api.cache ?? 30 * 1000;
|
||||
store.load(env, api, data);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
placeholder,
|
||||
render,
|
||||
classnames: cx,
|
||||
name,
|
||||
data,
|
||||
store
|
||||
} = this.props;
|
||||
const map = store.map;
|
||||
|
||||
let key =
|
||||
this.props.value ?? (name ? getVariable(data, name) : undefined);
|
||||
|
||||
let viewValue: React.ReactNode = (
|
||||
<span className="text-muted">{placeholder}</span>
|
||||
);
|
||||
|
||||
key =
|
||||
typeof key === 'string'
|
||||
? key.trim()
|
||||
: key === true
|
||||
? '1'
|
||||
: key === false
|
||||
? '0'
|
||||
: key; // trim 一下,干掉一些空白字符。
|
||||
|
||||
if (typeof key !== 'undefined' && map && (map[key] ?? map['*'])) {
|
||||
viewValue = render(
|
||||
'tpl',
|
||||
map[key] ?? map['*'] // 兼容平台旧用法:即 value 为 true 时映射 1 ,为 false 时映射 0
|
||||
);
|
||||
}
|
||||
|
||||
return <span className={cx('MappingField', className)}>{viewValue}</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)(?:map|mapping)$/,
|
||||
name: 'mapping'
|
||||
})
|
||||
export class MappingFieldRenderer extends MappingField {}
|
||||
export class MappingFieldRenderer extends React.Component<RendererProps> {
|
||||
render() {
|
||||
return <MappingField {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
@ -628,11 +628,11 @@ export const resolveVariable = (path?: string, data: any = {}): any => {
|
||||
}, data);
|
||||
};
|
||||
|
||||
export const isPureVariable = (path?: any) =>
|
||||
typeof path === 'string'
|
||||
export function isPureVariable(path?: any): path is string {
|
||||
return typeof path === 'string'
|
||||
? /^\$(?:([a-z0-9_.]+)|{[^}{]+})$/.test(path)
|
||||
: false;
|
||||
|
||||
}
|
||||
export const resolveVariableAndFilter = (
|
||||
path?: string,
|
||||
data: object = {},
|
||||
|
Loading…
Reference in New Issue
Block a user