工作台开发
一、 前言
工作台分为个人工作台和系统工作台(系统场景),个人工作台顾名思义就是用户自己定义的工作台,系统工作台就是由运维人员事先定义好的工作台,其它人可以直接使用(授权后可使用)。
工作台相当于一个容器,容器包含很多面板,每一个面板可以承载一个或多个组件,面板可以随意缩放大小,面板与面板之前可以相互联动。工作台或者面板只是提供一个壳,这个是框架已经支持了,但是里面的组件是需要开发。
二、 组件开发规范
工作台中的组件开发跟开发一个普通页面是没什么区别的,任何一个页面都可以放入工作台,但还是有些规范需要准守:
第一,组件设计阶段,需要知道组件的最佳展示效果,假设组件最终要放在一个四等分的工作台中(如下图),那么设计组件的时候,就需要按照这个尺寸来设计,这样开发出来的组件可以更好的在工作台进行展示。
第二,组件开发阶段,按照新增页面就可以快速开发一个组件,但需要注意的是,开发的时候需要按照设计师给的尺寸进行开发,可以通过缩小浏览器窗口来模拟组件的尺寸。 7 第三,组件开发完成之后,可以看一下在工作台的展示效果。
三、 事件总线
工作台内部自定义了八个通信通道,默认是白色通道。
组件实现通信有两种情况:
第一种是,ABCD 是不相同组件,可以使用自定义事件或者是使用系统内置通信通道。
- 工作台 1 中的组件 A 发生变化,将消息通知出去,同一个工作台中的组件 B 接收到事件,响应变化
- 事件仅局限于当前工作台,也即工作台 2 中同样的组件 B 是收不到事件的。
第二种是,BCD是同一个组件,但是只有 B 组件可以接收 A 组件的消息变化,只能使用系统内置通信通道。
- 工作台 1 中的组件 A 发生变化,将消息通知出去,同一个工作台中的组件 B 接收到事件,但 CD 组件不会收到信息,只要将通道换成不一致即可,如:AB 组件使用白色通道,CD 组件使用其他通道
1. 发送事件
API
工作台默认会传递 props 给插入的组件,props 结构如下:
{
workbenchId: '', // 工作台id
layoutItemId: '', // 面板id
linkageValue: '', // 通信通道
}
/*
* event 事件名称 / 通信通道
* data 携带的数据
* this 组件的实例
*/
window.globalStore.emitWorkbenchEvent(event, data, this);
代码参考(Class写法)
// 代码来源于示例代码app/pages/sample/quickStart/TodoList
export default class TodoList extends Component<any, IState> {
handleSubmit = (value: string) => {
const todos = this.state.todos;
const item = {
id: uniqueId(),
name: value
};
todos.push(item);
this.setState({ todos });
// 向另外一个窗口发出todo-list变动事件
this.emitTodoListChanged({ type: 'add', data: item });
};
// 测试工作台面板之间互联互通专用代码
emitTodoListChanged = (event: any) => {
// 1.自定义事件方式
window.globalStore.emitWorkbenchEvent('todo-list-changed', event, this);
// 2.使用系统内置通信通道
window.globalStore.emitWorkbenchEvent(this.props.linkageValue, event, this);
};
render() {
const { todos } = this.state;
return (
<div>
<Header onSubmit={this.handleSubmit} />
<Content todos={todos} onDelete={this.hanldeDelete} />
<Footer todos={todos} />
</div>
);
}
}
代码参考(Hooks写法)
const UserComponent = (props: any, ref: any) => {
// 测试工作台面板之间互联互通专用代码
const emitUserChanged = (type: string) => {
// 1.自定义事件方式
window.globalStore.emitWorkbenchEvent('todo-list-changed', { type }, ref.current);
// 2.使用系统内置通信通道
window.globalStore.emitWorkbenchEvent(props.linkageValue, { type }, ref.current);
};
const onClose = () => {
modal.close(); // 关闭模态框
tableStore.reload(true); // 刷新表格数据
emitUserChanged('addOrUpdate');
};
return (
<Fragment>
<div className="qx-main">
<AgGrid.SearchFormTable tableConfig={tableConfig} formConfig={formConfig} searchApi={searchApi}>
<Button size="small" onClick={showAddForm} type="primary">
新增
</Button>
</AgGrid.SearchFormTable>
</div>
<Modal store={modal}></Modal>
</Fragment>
);
};
// 这里为了能够在组件内部拿到ref属性,必须用forwardRef来封装
export default observer(React.forwardRef(UserComponent));
2. 接收事件
API
// 组件实例实现onWorkbenchEvent方法,返回需要接受的事件以及对应的处理函数
onWorkbenchEvent = () => {
return {
[eventNawme]: (event: any) => {}
};
};
代码参考(Class写法)
export default class TodoList extends Component<any, IState> {
// 监听来自另外一个窗口的todo-list-changed消息,同步更新数据
// 主要:只有在同一个工作台的其它窗口发送的消息才有效
// 这个只是为了测试工作台中面板之间的连通性
onWorkbenchEvent = () => {
return {
// 监听自定义事件
'todo-list-changed': (event: any) => {
const { type, data } = event;
if (type === 'add') {
const todos = this.state.todos;
todos.push(data);
this.setState({ todos: [...todos] });
} else if (type === 'remove') {
let todos = this.state.todos;
todos = todos.filter(item => item.id !== data.id);
this.setState({ todos: [...todos] });
}
},
// 监听系统内置通信通道
[`${this.props.linkageValue}`]: (data: any) => {
console.log(data);
},
};
};
render() {
const { todos } = this.state;
return (
<div>
<Header onSubmit={this.handleSubmit} />
<Content todos={todos} onDelete={this.hanldeDelete} />
<Footer todos={todos} />
</div>
);
}
}
代码参考(Hooks写法)
const UserComponent = (props: any, ref: any) => {
// 要使父组件能够通过useRef或者createRef拿到组件实例并调用组件方法,
// 必须使用useImperativeHandle配合forwardRef函数
React.useImperativeHandle(ref, () => {
return {
props,
onWorkbenchEvent: () => {
return {
'workbench-user-changed': (data: any) => {
tableStore.reload();
},
// 监听系统内置通信通道
[`${props.linkageValue}`]: (data: any) => {
console.log(data);
},
};
}
};
});
return (
<Fragment>
<div className="qx-main">
<AgGrid.SearchFormTable
tableConfig={tableConfig}
formConfig={formConfig}
searchApi={searchApi}
></AgGrid.SearchFormTable>
</div>
<Modal store={modal}></Modal>
</Fragment>
);
};
// 这里为了能够在组件内部拿到ref属性,必须用forwardRef来封装
export default observer(React.forwardRef(UserComponent));
四、 iframe 事件总线
- 工作台 1 中的子系统 A 发生变化,将消息通知出去,同一个工作台中的子系统 B 接收到事件,响应变化
- 事件既可以仅局限于当前工作台,即在其它工作台中同样的子系统 B 是收不到事件的。
- 事件也可以不局限于当前工作台,即在其它工作台中同样的子系统 B 也能收到事件。
1. 发送事件
示例:子系统 A 发送【用户变化】事件
sendWorkbenchEvent = props => {
window.isInWorkbench &&
window.parent.postMessage(
{
// 类型,标识为工作台事件,写死
type: 'workbenchEvent', // 类型,标识为工作台事件
// 目标应用Id,向那个应用发送事件,如果不指定,则向所有iframe发送事件
appId: 'godzillaPro2',
// 工作台Id,这个参数会在加载iframe的时候,通过url传给子系统
// 指定事件只会向所在的工作台中的其它iframe发送,如果不指定,则向所有iframe发送
// window._workbenchId,这个变量就是从url获取到,然后存储在window对象
workbenchId: window._workbenchId,
// 面板Id,,这个参数会在加载iframe的时候,通过url传给子系统
// 指定事件不会向自身应用发送,也即自身即使监听了该事件,也不会收到事件
// window._layoutItemId,这个变量就是从url获取到,然后存储在window对象
layoutItemId: window._layoutItemId,
// 事件名称,可以随意指定,但要确保唯一,事件要有含义
eventName: 'user-data-changed',
// 携带的业务数据,对象存储
props: {
type: 'addUser',
data: {} // user数据
} // 携带的业务数据
},
'*'
);
};
2. 接收事件
示例:子系统 B 响应【用户变化】事件
window.addEventListener(
'message',
event => {
const { type, eventName, props } = event.data;
if (type === 'workbenchEvent') {
if (eventName === 'user-data-changed') {
// 响应事件逻辑
console.log(data);
}
}
},
true
);