继续写第2章
This commit is contained in:
parent
c9d9e2c6cd
commit
0a896349f7
|
|
@ -14,4 +14,5 @@
|
|||
- [编写组件](./chapter2/2-3.md)
|
||||
- [编写详情组件](./chapter2/2-3-1.md)
|
||||
- [编写更新组件](./chapter2/2-3-2.md)
|
||||
- [编写列表组件](./chapter2/2-3-3.md)
|
||||
- [编写列表组件](./chapter2/2-3-3.md)
|
||||
- [组织组件](./chapter2/2-3-4.md)
|
||||
|
|
@ -1,2 +1,139 @@
|
|||
# 编写列表组件
|
||||
|
||||
接下来我们来看看列表组件的例子。在system/panel组件里,我们不仅想管理当前*System*的信息,还想管理属于它的*Application*对象信息,回忆我们在介绍[*system/detail组件*](2-3-1.md)中的这张图:
|
||||
|
||||

|
||||
|
||||
在左侧点击“应用管理”时,我们希望展示(属于这个*System*)的所有*Application*的信息列表,这是一个列表组件,其代码见*oak-general-business/src/components/system/application*目录。
|
||||
|
||||
### 逻辑层
|
||||
在*index.ts*中,我们要声明这是一个List页面(*isList*属性为true),给出完整的projection。
|
||||
```typescript
|
||||
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*中也有很多设置需要展示)。
|
||||
|
||||
```typescript
|
||||
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的主键)```约束才对(可以到[这里](2-2.md)复习一下这两个数据结构之间的关系)。没错,这个问题非常关键。在list组件中,我们可以通过*filters*域来定义对数据的查询约束,像下面这样:
|
||||
```typescript
|
||||
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*)
|
||||
```typescript
|
||||
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*)
|
||||
```typescript
|
||||
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来标定两者之间的关系:
|
||||
```typescript
|
||||
...
|
||||
{
|
||||
...
|
||||
children: (
|
||||
<ApplicationList
|
||||
oakPath={`${oakFullpath}.application$system`}
|
||||
systemId={id}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...
|
||||
```
|
||||
用了这样的定义,相当于告诉框架“此applicationList组件所查询的*Application*,是关联在其父结点的*System*对象上(框架会要求其查询数据时符合```systemId = 父system.id```的约束)。
|
||||
|
||||
|
||||
当你理解了上面这个例子,就会意识到,绝大多数的页面中的组件关系,和它们所属的对象关系是完全对应的。通过仔细划分这些对象到子组件上,可以让整体代码组织的相当优雅和易于维护。在下一章节中,我们将进一步描述这种关系的细节。
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# 组织组件
|
||||
|
||||
### 父子组件的关系
|
||||
组件和其子组件之间有三种典型的关系:
|
||||
|
||||
* 一对一:父亲是一个*detail/upsert*组件,其子组件也是一个*detail/upsert*组件。例如:假设我们设计了一个*Application*的详情页面,其中也要渲染其关联的*System*信息,则可以分成两个组件编写,并通过设置```<SystemDetail oakPath={`${oakFullpath}.system}` />```来指定关系。
|
||||
* 一对多:父亲是一个*detail/upsert*组件,其子组件是一个*list*组件。在上一小节的例子里就是这种关系,一个*System*的详情组件包含了一个*Application*的列表组件。
|
||||
* 多对一:父亲是一个*list*组件,其子组件是一个*detail/upsert*组件。这种关系是最特殊的一种,此时父子组件所属的*Entity*一定是相等的,且它们的相对路径是子组件对象的主键:```<SystemItem oakPath={`${oakFullpath}.${system.id}`} />```
|
||||
|
||||
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
# 组件编写
|
||||
定义完*Entity*后,可以马上开始编写组件/页面(以下统一用“组件”作为主语)。
|
||||
|
||||
在编写组件前,首先要明确一个概念:用Oak框架编写业务应用,其中几乎所有的组件和绝大部分页面都关联在某一个*Entity*之上,实现对此实体的增删改查的相关功能。根据多年的项目经验,我们把某个组件对*Entity*的操作划分为以下三种类型:
|
||||
* **Get List**:列表页,查询并显示此*Entity*的多条数据
|
||||
* **Get Detail**:详情页,查询并显示此*Entity*的(指定id的)某条数据
|
||||
在编写组件前,首先要明确一个概念:用Oak框架编写业务应用,其中几乎所有的组件和绝大部分页面都关联在某一个*Entity*之上,根据组件的功能特征,我们可以将这类**Entity组件**划分为以下三种类型:
|
||||
* **List**:列表页,查询并显示此*Entity*的多条数据
|
||||
* **Detail**:详情页,查询并显示此*Entity*的(指定id的)某条数据
|
||||
* **Upsert**:更新页,查询并对此*Entity*的(指定id的)某条数据进行更新,或者创建一条新的数据
|
||||
|
||||
下面将以oak-general-business包当中的System和Application这两个Entity作为示例,介绍如何实现组件逻辑层。
|
||||
下面将以*oak-general-business*包当中的*System*和*Application*这两个*Entity*作为示例,介绍如何编写组件。
|
||||
|
||||
oak-general-business是Oak框架的一个基础业务逻辑包,在其中按照Oak框架的规范,实现了大量较为底层的功能逻辑,如用户管理、应用管理、授权管理、消息管理,文件管理等等。关于oak-general-business包的详细介绍请参见:todo。在这里,我们只需要了解Application和System这两个对象的意义和大概结构,在oak-general-business中,System代表一个业务系统(就是你要开发的这个业务系统),而Application代表业务系统的一个前端应用。两者的数据结构大致如下(代码来源于oak-general-business/src/entities,为了简化去掉了一些业务类属性):
|
||||
*oak-general-business*是Oak框架的一个基础业务逻辑包,在其中按照Oak框架的规范,实现了大量较为底层的功能逻辑,如用户管理、应用管理、授权管理、消息管理,文件管理等等。关于oak-general-business包的详细介绍请参见:todo。在这里,我们只需要了解*Application*和*System*这两个对象的意义和大概结构,在*oak-general-business*中,*System*代表一个业务系统(就是你正在开发的这个业务系统),而*Application*代表业务系统的一个前端应用。两者的数据结构大致如下(代码来源于```oak-general-business/src/entities```,为了简化去掉了一些属性):
|
||||
|
||||
* Application
|
||||
```typescript
|
||||
|
|
@ -32,4 +32,4 @@ export interface Schema extends EntityShape {
|
|||
};
|
||||
```
|
||||
|
||||
在系统上线前,您需要配置相应的Application和System,例如小程序appId、网站域名之类的属性。在这里我们并不过多介绍这两个对象的作用,只需要知道,Oak应用本身也是通过这两个Entity来进行管理的,因此超级管理员需要对它们进行相应的管理操作,我们接下来就结合代码描述这些相关组件的实现。
|
||||
在系统上线前,您需要配置相应的*Application*和*System*,例如小程序appId/appsecret、网站域名之类的属性。在这里我们并不过多介绍这两个对象的作用,只需要知道,Oak应用本身也是通过这两个*Entity*来进行管理的,超级管理员在系统正式运行之前需要对它们进行相应的管理操作(通过*oak-general-business*提供的这些组件。而在绝大多数业务系统中,这些参数都是通过config文件配置为系统的全局变量,这显然不如将之也当成业务对象来管理更加灵活、通用)。我们接下来就结合代码描述这些相关组件的实现。
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
Loading…
Reference in New Issue