Skip to main content
Version: 2.x

工作台开发

一、 前言

工作台分为个人工作台和系统工作台(系统场景),个人工作台顾名思义就是用户自己定义的工作台,系统工作台就是由运维人员事先定义好的工作台,其它人可以直接使用(授权后可使用)。

工作台相当于一个容器,容器包含很多面板,每一个面板可以承载一个或多个组件,面板可以随意缩放大小,面板与面板之前可以相互联动。工作台或者面板只是提供一个壳,这个是框架已经支持了,但是里面的组件是需要开发。

二、 组件开发规范

工作台中的组件开发跟开发一个普通页面是没什么区别的,任何一个页面都可以放入工作台,但还是有些规范需要遵守:

第一,组件设计阶段,需要知道组件的最佳展示效果,假设组件最终要放在一个四等分的工作台中(如下图),那么设计组件的时候,就需要按照这个尺寸来设计,这样开发出来的组件可以更好的在工作台进行展示。

第二,组件开发阶段,按照新增页面就可以快速开发一个组件,但需要注意的是,开发的时候需要按照设计师给的尺寸进行开发,可以通过缩小浏览器窗口来模拟组件的尺寸。

第三,组件开发完成之后,可以看一下在工作台的展示效果。

PNG

三、 事件总线

工作台内部自定义了八个通信通道,默认是白色通道。

PNG

PNG 组件实现通信有两种情况:

第一种是,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 事件总线

PNG

  • 工作台 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
);

五、 工作台组件的参数存储

v 2.1.14 以上版本可用

实现原理图

工作台组件的参数存储实现原理

  • 实现在工作台上保存组件的参数,其中工作台组件包含,portal 自带组件、 gza 微应用组件和 gza 子应用组件。
  • 工作台提供了两种传递组件参数的方式,提供了一个保存组件参数的 API。
  • 工作台还实现了自动保存组件参数的功能,需要在新建或编辑工作台时设置。
  • 组件参数只能在组件首次渲染时取到。
  • 工作台使用点击“关闭组件”按钮后组件参数会跟随组件一起删除。

1. props

使用 props 可以在组件中直接引用,这种方式对于 portal 自带组件和 gza 微应用组件没有什么限制,但是对于 gza 子应用组件有长度限制,当 params 长度大于 2k 字节时,不建议使用。

props 还有一个优点,可以在组件加载完成之前获取到 params,例如使用 props 可以在表格自动加载数据前设置参数。

props = {
...
params: Object, // 组件参数
layoutItemId: String, // 工作台面板Id
workbenchId: String // 工作台Id
...
}

2. onWorkbenchEvent

onWorkbenchEvent 是另一种传递方式的载体,使用事件回调的方式传递组件数据,想要接收到参数必须按照下面的用法配置。

onWorkbenchEvent 可以传递 params 长度大于 2k 字节,但是有一个缺点,只能在组件完全加载之后才能获取到 params。

export default class TodoList extends Component {
// componentParams 是系统内部定义的默认事件,业务开发人员无需关心。
onWorkbenchEvent = () => {
return {
// 监听 componentParams 事件
'componentParams': (data) => {
console.log('组件参数',data);
},
};
};

}
const UserComponent = (props, ref) => {
// componentParams 是系统内部定义的默认事件,业务开发人员无需关心。
React.useImperativeHandle(ref, () => {
return {
props,
onWorkbenchEvent: () => {
return {
// 监听 componentParams 事件
'componentParams': (data) => {
console.log('组件参数',data);
}
};
}
};
});

};

// 这里为了能够在组件内部拿到ref属性,必须用forwardRef来封装
export default React.forwardRef(UserComponent);

3. addWorkbenchComParams

addWorkbenchComParams 是 globalStore 的方法,通过调用此方法向 portal 传递组件参数。工作台组件的所有组件参数均在 portal 临时存储,用户可以根据需求设置工作台组件参数的存储方式。

type Config = { layoutItemId: string; workbenchId: string };
type addWorkbenchComParams = (config: Config, params: Object) => void;
class Command extends Component {
componentDidMount() {
console.log('组件参数',this.props.layoutItemId,this.props.params);
}

handleSubmit = async (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
// 注意!!!isInWorkbench 判断当前组件是否在工作台打开,没有这层判断,当组件在 tab 打开时会报错,因为 globalStore 没有注册
if (window.isInWorkbench ){
window.globalStore.addWorkbenchComParams(
{
layoutItemId: this.props.layoutItemId,
workbenchId: this.props.workbenchId,
},
{
...values
}
)
}

}
});
};

render() {
return (
<Form onSubmit={this.handleSubmit}>
<Form.Item label='指令编码'>
{getFieldDecorator('instCode', {
rules: [ { required: true, message: '请输入指令编码' } ],
})( <Input/> )}
</Form.Item>
<Form.Item style={{textAlign: 'right'}}>
<Button type="primary" htmlType="submit">
发送
</Button>
</Form.Item>
</Form>
)
}
}

const Com = Form.create({})(Command);
export default React.forwardRef((props,ref)=> {
return <Com wrappedComponentRef={ref} {...props}/>
});

addWorkbenchComParams 后有两种情况。

情况一:在新增或编辑工作台的时候自动保存组件参数没有勾选,调用 addWorkbenchComParams 后需要操作人员手动点击保存,组件参数才会保存生效。 手动保存工作台 手动保存工作台

情况二:在新增或编辑工作台的时候自动保存组件参数已经勾选,调用 addWorkbenchComParams 后无需操作人员手动点击保存,系统内部会自动保存组件参数。 自动保存工作台