Merge remote-tracking branch 'baidu/master'

This commit is contained in:
liaoxuezhi 2021-01-29 23:19:33 +08:00
commit 99a58e259a
14 changed files with 174 additions and 14 deletions

View File

@ -192,3 +192,7 @@ Service 中的`api`和`schemaApi`都支持**接口联动**。
}
}
```
## 通过 WebSocket 实时更新数据
请参考 [service](../../service) 中的内容。

View File

@ -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) 里的配置
## 基本使用

View File

@ -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 |

View File

@ -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 的其它亮点

View File

@ -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/)」,即便完全不懂前端也能基于它开发应用。

View File

@ -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`

View File

@ -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;

View File

@ -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'

View File

@ -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>

View File

@ -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 =

View File

@ -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) {

View File

@ -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({

View File

@ -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}

View File

@ -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