mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 20:18:03 +08:00
Merge remote-tracking branch 'baidu/master'
This commit is contained in:
commit
99a58e259a
@ -192,3 +192,7 @@ Service 中的`api`和`schemaApi`都支持**接口联动**。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 通过 WebSocket 实时更新数据
|
||||
|
||||
请参考 [service](../../service) 中的内容。
|
||||
|
@ -8,7 +8,7 @@ icon:
|
||||
order: 50
|
||||
---
|
||||
|
||||
> 在 React 项目中使用 Icon 需要引入 `font-awesome@4.7.0`,然后在代码中 `import 'font-awesome/css/font-awesome.css'`,还有相关的 webpack 配置,具体请参考 [amis-react-starter](https://github.com/fex-team/amis-react-starter) 里的配置
|
||||
> 在 React 项目中使用 Icon 需要引入 `font-awesome@4.7.0`,然后在代码中 `import 'font-awesome/css/font-awesome.css'`,还有相关的 webpack 配置,具体请参考 [amis-react-starter](https://github.com/aisuda/amis-react-starter) 里的配置
|
||||
|
||||
## 基本使用
|
||||
|
||||
|
@ -281,7 +281,71 @@ amis 中部分组件,作为展示组件,自身没有**使用接口初始化
|
||||
|
||||
## 定时轮询刷新
|
||||
|
||||
设置 `interval` 可以定时刷新,单位是毫秒,最小间隔是 1 秒。
|
||||
设置 `interval` 可以定时刷新 api 接口,单位是毫秒,最小间隔是 1 秒。
|
||||
|
||||
## 通过 WebSocket 实时获取数据
|
||||
|
||||
Service 支持通过 WebSocket 获取数据,只需要设置 ws(由于无示例服务,所以无法在线演示)。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service",
|
||||
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/page/initData",
|
||||
"ws": "ws://localhost:8777",
|
||||
"body": {
|
||||
"type": "panel",
|
||||
"title": "$title",
|
||||
"body": "随机数:${random}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
可以只设置 ws,通过 ws 来获取所有数据,也可以同时设置 api 和 ws,让 api 用于获取全部数据,而 ws 用于获取实时更新的数据。
|
||||
|
||||
后端实现示例,基于 [ws](https://github.com/websockets/ws):
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket.Server({port: 8777});
|
||||
|
||||
ws.on('connection', function connection(ws) {
|
||||
setInterval(() => {
|
||||
const random = Math.floor(Math.random() * Math.floor(100));
|
||||
// 返回给 amis 的数据
|
||||
const data = {
|
||||
random
|
||||
};
|
||||
// 发送前需要转成字符串
|
||||
ws.send(JSON.stringify(data));
|
||||
}, 500);
|
||||
});
|
||||
```
|
||||
|
||||
WebSocket 客户端的默认实现是使用标准 WebSocket,如果后端使用定制的 WebSocket,比如 socket.io,可以通过覆盖 `env.wsFetcher` 来自己实现数据获取方法,默认实现是:
|
||||
|
||||
```javascript
|
||||
wsFetcher(ws, onMessage, onError) {
|
||||
if (ws) {
|
||||
const socket = new WebSocket(ws);
|
||||
socket.onmessage = (event: any) => {
|
||||
if (event.data) {
|
||||
onMessage(JSON.parse(event.data));
|
||||
}
|
||||
};
|
||||
socket.onerror = onError;
|
||||
return {
|
||||
close: socket.close
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
close: () => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过 onMessage 来通知 amis 数据修改了,并返回 close 函数来关闭连接。
|
||||
|
||||
## 属性表
|
||||
|
||||
@ -291,6 +355,7 @@ amis 中部分组件,作为展示组件,自身没有**使用接口初始化
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| body | [SchemaNode](../types/schemanode) | | 内容容器 |
|
||||
| api | [api](../types/api) | | 初始化数据域接口地址 |
|
||||
| ws | `string` | | WebScocket 地址 |
|
||||
| initFetch | `boolean` | | 是否默认拉取 |
|
||||
| schemaApi | [api](../types/api) | | 用来获取远程 Schema 接口地址 |
|
||||
| initFetchSchema | `boolean` | | 是否默认拉取 Schema |
|
||||
|
@ -200,7 +200,7 @@ amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可
|
||||
- **不需要懂前端**:在百度内部,大部分 amis 用户之前从来没写过前端页面,也不会 `JavaScript`,却能做出专业且复杂的后台界面,这是所有其他前端 UI 库都无法做到的;
|
||||
- **不受前端技术更新的影响**:百度内部最老的 amis 页面是 4 年多前创建的,至今还在使用,而当年的 `Angular/Vue/React` 版本现在都废弃了,当年流行的 `Gulp` 也被 `Webpack` 取代了,如果这些页面不是用 amis,现在的维护成本会很高;
|
||||
- **享受 amis 的不断升级**:amis 一直在提升细节交互体验,比如表格首行冻结、下拉框大数据下不卡顿等,之前的 JSON 配置完全不需要修改;
|
||||
- 可以 **完全** 使用 [可视化页面编辑器](https://fex-team.github.io/amis-editor-demo/) 来制作页面:一般前端可视化编辑器只能用来做静态原型,而 amis 可视化编辑器做出的页面是可以直接上线的。
|
||||
- 可以 **完全** 使用 [可视化页面编辑器](https://aisuda.github.io/amis-editor-demo/) 来制作页面:一般前端可视化编辑器只能用来做静态原型,而 amis 可视化编辑器做出的页面是可以直接上线的。
|
||||
|
||||
## amis 的其它亮点
|
||||
|
||||
|
@ -4,7 +4,7 @@ title: 常见问题
|
||||
|
||||
## 如何实现左侧导航栏页面跳转?
|
||||
|
||||
在 1.1.1 之后的版本提供了新的 app 组件,可以基于它实现导航功能,请参考 `https://github.com/fex-team/amis-admin` 项目。
|
||||
在 1.1.1 之后的版本提供了新的 app 组件,可以基于它实现导航功能,请参考 `https://github.com/aisuda/amis-admin` 项目。
|
||||
|
||||
另外 amis 团队还开发了「[爱速搭](http://suda.baidu.com/)」,即便完全不懂前端也能基于它开发应用。
|
||||
|
||||
|
@ -211,7 +211,7 @@ amis.embed(
|
||||
|
||||
## react
|
||||
|
||||
初始项目请参考 <https://github.com/fex-team/amis-react-starter>。
|
||||
初始项目请参考 <https://github.com/aisuda/amis-react-starter>。
|
||||
|
||||
如果在已有项目中,React 版本需要是 `^16.8.6`,mobx 需要 `^4.5.0`。
|
||||
|
||||
|
@ -804,15 +804,21 @@ export class TreeSelector extends React.Component<
|
||||
export function findAncestorsWithValue(
|
||||
ancestors: any[],
|
||||
options: any[],
|
||||
value: any
|
||||
value: any,
|
||||
valueField = 'value'
|
||||
) {
|
||||
for (let option of options) {
|
||||
if (option.value === value) {
|
||||
if (option[valueField] === value) {
|
||||
return true;
|
||||
}
|
||||
// 如果没有就在 children 中查找
|
||||
if (option.children) {
|
||||
const inChild = findAncestorsWithValue(ancestors, option.children, value);
|
||||
const inChild = findAncestorsWithValue(
|
||||
ancestors,
|
||||
option.children,
|
||||
value,
|
||||
valueField
|
||||
);
|
||||
if (inChild) {
|
||||
ancestors.unshift(option);
|
||||
return true;
|
||||
|
@ -123,6 +123,11 @@ export interface RenderSchemaFilter {
|
||||
export interface RenderOptions {
|
||||
session?: string;
|
||||
fetcher?: (config: fetcherConfig) => Promise<fetcherResult>;
|
||||
wsFetcher?: (
|
||||
ws: string,
|
||||
onMessage: (data: any) => void,
|
||||
onError: (error: any) => void
|
||||
) => void;
|
||||
isCancel?: (value: any) => boolean;
|
||||
notify?: (
|
||||
type: 'error' | 'success',
|
||||
@ -266,6 +271,25 @@ const defaultOptions: RenderOptions = {
|
||||
fetcher() {
|
||||
return Promise.reject('fetcher is required');
|
||||
},
|
||||
// 使用 WebSocket 来实时获取数据
|
||||
wsFetcher(ws, onMessage, onError) {
|
||||
if (ws) {
|
||||
const socket = new WebSocket(ws);
|
||||
socket.onmessage = (event: any) => {
|
||||
if (event.data) {
|
||||
onMessage(JSON.parse(event.data));
|
||||
}
|
||||
};
|
||||
socket.onerror = onError;
|
||||
return {
|
||||
close: socket.close
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
close: () => {}
|
||||
};
|
||||
}
|
||||
},
|
||||
isCancel() {
|
||||
console.error(
|
||||
'Please implements this. see https://baidu.gitee.io/amis/docs/start/getting-started#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97'
|
||||
|
@ -92,8 +92,9 @@ export default class NumberControl extends React.Component<NumberProps, any> {
|
||||
|
||||
let precisionProps: any = {};
|
||||
|
||||
if (typeof precision === 'number') {
|
||||
precisionProps.precision = precision;
|
||||
const finalPrecision = this.filterNum(precision);
|
||||
if (typeof finalPrecision === 'number') {
|
||||
precisionProps.precision = finalPrecision;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -106,7 +107,7 @@ export default class NumberControl extends React.Component<NumberProps, any> {
|
||||
onChange={this.handleChange}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
precision={precision}
|
||||
precision={finalPrecision}
|
||||
showSteps={showSteps}
|
||||
/>
|
||||
</div>
|
||||
|
@ -410,10 +410,15 @@ export default class TreeSelectControl extends React.Component<
|
||||
|
||||
@autobind
|
||||
renderItem(item: Option) {
|
||||
const {labelField} = this.props;
|
||||
const {labelField, valueField} = this.props;
|
||||
// 将所有祖先节点也展现出来
|
||||
const ancestors: any[] = [];
|
||||
findAncestorsWithValue(ancestors, this.props.options, item.value);
|
||||
findAncestorsWithValue(
|
||||
ancestors,
|
||||
this.props.options,
|
||||
item[valueField || 'value'],
|
||||
valueField
|
||||
);
|
||||
let ancestorsLabel = '';
|
||||
if (ancestors.length) {
|
||||
ancestorsLabel =
|
||||
|
@ -34,6 +34,11 @@ export interface ServiceSchema extends BaseSchema {
|
||||
*/
|
||||
api?: SchemaApi;
|
||||
|
||||
/**
|
||||
* WebScocket 地址,用于实时获取数据
|
||||
*/
|
||||
ws?: string;
|
||||
|
||||
/**
|
||||
* 内容区域
|
||||
*/
|
||||
@ -100,6 +105,9 @@ export default class Service extends React.Component<ServiceProps> {
|
||||
timer: NodeJS.Timeout;
|
||||
mounted: boolean;
|
||||
|
||||
// 主要是用于关闭 socket
|
||||
socket: any;
|
||||
|
||||
static defaultProps: Partial<ServiceProps> = {
|
||||
messages: {
|
||||
fetchFailed: 'fetchFailed'
|
||||
@ -151,11 +159,21 @@ export default class Service extends React.Component<ServiceProps> {
|
||||
errorMessage: fetchFailed
|
||||
})
|
||||
.then(this.afterSchemaFetch);
|
||||
|
||||
if (props.ws && prevProps.ws !== props.ws) {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
}
|
||||
this.socket = store.fetchWSData(props.ws, this.afterDataFetch);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
clearTimeout(this.timer);
|
||||
if (this.socket && this.socket.close) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
@ -164,6 +182,7 @@ export default class Service extends React.Component<ServiceProps> {
|
||||
schemaApi,
|
||||
initFetchSchema,
|
||||
api,
|
||||
ws,
|
||||
initFetch,
|
||||
initFetchOn,
|
||||
store,
|
||||
@ -187,6 +206,10 @@ export default class Service extends React.Component<ServiceProps> {
|
||||
})
|
||||
.then(this.afterDataFetch);
|
||||
}
|
||||
|
||||
if (ws) {
|
||||
this.socket = store.fetchWSData(ws, this.afterDataFetch);
|
||||
}
|
||||
}
|
||||
|
||||
afterDataFetch(data: any) {
|
||||
|
@ -197,9 +197,15 @@ export class TableBody extends React.Component<TableBodyProps> {
|
||||
result[0].colSpan = (result[0].colSpan || 1) + 1;
|
||||
}
|
||||
|
||||
// 如果是展开栏,让它和下一列合并。
|
||||
if (columns[0].type === '__expandme' && result[0]) {
|
||||
result[0].colSpan = (result[0].colSpan || 1) + 1;
|
||||
}
|
||||
|
||||
// 缺少的单元格补齐
|
||||
const appendLen =
|
||||
filterColumns.length - result.reduce((p, c) => p + (c.colSpan || 1), 0);
|
||||
columns.length - result.reduce((p, c) => p + (c.colSpan || 1), 0);
|
||||
|
||||
if (appendLen) {
|
||||
const item = result.pop();
|
||||
result.push({
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
SchemaIcon
|
||||
} from '../Schema';
|
||||
import {ActionSchema} from './Action';
|
||||
import {filter} from '../utils/tpl';
|
||||
|
||||
export interface TabSchema extends Omit<BaseSchema, 'type'> {
|
||||
/**
|
||||
@ -323,6 +324,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
|
||||
isVisible(tab, data) ? (
|
||||
<Tab
|
||||
{...(tab as any)}
|
||||
title={filter(tab.title, data)}
|
||||
disabled={isDisabled(tab, data)}
|
||||
key={index}
|
||||
eventKey={tab.hash || index}
|
||||
|
@ -140,6 +140,28 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
});
|
||||
|
||||
const fetchWSData = (ws: string, afterDataFetch: (data: any) => any) => {
|
||||
const env = getEnv(self);
|
||||
|
||||
env.wsFetcher(
|
||||
ws,
|
||||
(data: any) => {
|
||||
self.updateData(data, undefined, false);
|
||||
self.setHasRemoteData();
|
||||
// 因为 WebSocket 只会获取纯数据,所以没有 msg 之类的
|
||||
afterDataFetch({ok: true, data: data});
|
||||
},
|
||||
(error: any) => {
|
||||
updateMessage(error, true);
|
||||
env.notify('error', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const setHasRemoteData = () => {
|
||||
self.hasRemoteData = true;
|
||||
};
|
||||
|
||||
const fetchData: (
|
||||
api: Api,
|
||||
data?: object,
|
||||
@ -468,9 +490,11 @@ export const ServiceStore = iRendererStore
|
||||
markBusying,
|
||||
fetchInitData,
|
||||
fetchData,
|
||||
fetchWSData,
|
||||
reInitData,
|
||||
updateMessage,
|
||||
clearMessage,
|
||||
setHasRemoteData,
|
||||
saveRemote,
|
||||
fetchSchema,
|
||||
checkRemote
|
||||
|
Loading…
Reference in New Issue
Block a user