编写列表组件
接下来我们来看看列表组件的例子。在system/panel组件里,我们不仅想管理当前System的信息,还想管理属于它的Application对象信息,回忆我们在介绍system/detail组件中的这张图:

在左侧点击“应用管理”时,我们希望展示(属于这个System)的所有Application的信息列表,这是一个列表组件,其代码见oak-general-business/src/components/system/application目录。
逻辑层
在index.ts中,我们要声明这是一个List页面(isList属性为true),给出完整的projection。
export default OakComponent({
entity: 'application',
isList: true,
projection: {
id: 1,
name: 1,
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
formData({ data }) {
return {
applications: data || [],
};
},
});
注意,此时在formData中,data对象就是一个数组,其结构可以参见TypeScript的类型定义。
渲染层
渲染层和详情页面也是类似,在这里取得了props.data中的相关属性以后即可进行渲染(注意在上面的页面里,我们把Application的三行数据又渲染成了一行tabs页,因为在每个Application中也有很多设置需要展示)。
import React, { useState } from 'react';
import { Tabs, Modal, Button, Space } from 'antd';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function render(props: WebComponentProps<EntityDict, 'application', true, {
applications: EntityDict['application']['OpSchema'][];
systemId: string;
}>) {
const { oakFullpath, applications, oakExecutable, oakExecuting, systemId } = props.data;
if (oakFullpath && applications?.length > 0) {
return (
<Tabs>
{
applications.map(
ele => <Tab>....</Tab>
)
}
</Tabs>
);
}
return null;
}
组件的相对路径
在上面的例子中,你可能会意识到一个问题,详情组件和更新组件在初始props中会自带主键(oakId),列表组件在定义查询数据的时候难道没有约束条件吗?比如上面的system/application组件,所查询的Application对象应当是满足systemId=XXXXX(system的主键)约束才对(可以到这里复习一下这两个数据结构之间的关系)。没错,这个问题非常关键。在list组件中,我们可以通过filters域来定义对数据的查询约束,像下面这样:
export default OakComponent({
entity: 'application',
...,
properties: {
systemId: '', // 定义组件接受一个类型为string的属性systemId
}
filters: [
{
filter() {
const { systemId } = this.props; // 从this.props中可以取到对应的systemId
return {
systemId,
}; // 返回的filter要满足对应Entity上的定义。
}
}
],
});
不过在Oak框架里,我们更推荐用另一种方式来处理主外键之间的关系,就是通过设置组件之间的相对路径来实现。我们查看oak-app-domain中编译出来的Application对象声明,其Schema中有这样的定义:
(oak-general-business/src/oak-app-domain/Appliction/Schema.ts)
export type Schema = EntityShape & {
name: String<32>;
description: Text;
type: AppType;
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
domainId?: ForeignKey<"domain"> | null;
system: System.Schema;
...
};
同样,在System的Schema定义中也有:
(oak-general-business/src/oak-app-domain/System/Schema.ts)
export type Schema = EntityShape & {
name: String<32>;
description: Text;
config: Config;
platformId?: ForeignKey<"platform"> | null;
folder: String<16>;
super?: Boolean | null;
style?: Style | null;
entity?: String<32> | null;
entityId?: String<64> | null;
application$system?: Array<Application.Schema>;
...
这表示,在Application中,其system属性是一个System对象,而在System对象中,其application$system属性是Application对象的数组。在Oak框架中,这种对象之间的关系被贯彻在方方面面。在这里,我们在组件之间也可以应用这种关系,来定义组件(关联的对象)之间是什么关系。
在上面这个例子里,我们可以在system/panel组件中像这样来使用system/application组件,用oakPath来标定两者之间的关系:
...
{
...
children: (
<ApplicationList
oakPath={`${oakFullpath}.application$system`}
systemId={id}
/>
),
},
...
用了这样的定义,相当于告诉框架“此applicationList组件所查询的Application,是关联在其父结点的System对象上(框架会要求其查询数据时符合systemId = 父system.id的约束)。
当你理解了上面这个例子,就会意识到,绝大多数的页面中的组件关系,和它们所属的对象关系是完全对应的。通过仔细划分这些对象到子组件上,可以让整体代码组织的相当优雅,并且易于重用和维护。在下一章节中,我们将进一步描述这种关系的细节。