通常意义上,我们在编写对应某个资源的管理页面的时候,我们往往会面对一大类功能大致相似的需求,例如:

传统意义上的curd

带筛选条件的查询

更新某个具体的值

对某个具体的值进行更新

与回收站的交互操作

....等等一系列的操作

在此基础上,我们可能还需要对具体的权限管理系统进行相应的对接。从而完成以下操作:

资源的所有权管理


键入'/'获得帮助

考虑面对如此大量的相似需求,我们可以通过编写一系列工具库来完成。

主要页面框架

资源页面通常意义上以表格为呈现主体。其大致的页面布局为

html
...
<>
<ExtraActionToolbar></ExtraActionToolbar>
<QueryForm></QueryForm>
<MainActionToolbar></MainActionToolbar>
<Table></Table>
<UpdateItemModal></UpdateItemModal>
<CreateItemModal></CreateItemModal>
<TrashModal></TrashModal>
</>

其中,涉及以下功能:

mutation相关:

创建新Item

更新Item

回收站功能

将Item移动到回收站

单个

选中

全部

将回收站中的Item还原

单个

选中

全部

彻底删除

单个

选中

全部

对tem进行某类具体的操作

单个

选中

全部


键入'/'获得帮助

query相关:

查询条件的key化

区分回收站内外(软删除相关)

查询场景的通用配置参数

通用操作接口

对Item操作大致可以分为一下几类:

tsx
...
interface ItemMutation{
updateItem(id:number,data:any[])=>Promise<Item>;
updateSelectedItem(ids:number[],data:[])=>Promise<number>;
updateManyItems(where:number[],data[])=>Promise<number>;
};

在gql层面,我们受到如下限制:

无法同时更新跨表的链接信息(relation),只允许单个修改

对于修改的值({[key]:value}),存在如下格式要求:

对于String|Number|Boolean 类型的变量,需要构造{[key]:{set:value}}的格式。

对于关联关系,

对于关联的一个,需要构造{[key]:{connect:{id:value.id}}}的格式,此时value一定是ID的类型。

对于关联的多个,无法同时更新!!!

对于Object|Array的值,不做改变。(它们在数据库中以JSON为存储值)

故我们需要对于更新值,按照其原本的类型进行转化。

tsx
...
declare const convertUpdateData = (
data: ItemUpdateDataRaw,
{
jsonFields,
relationFields,
}: {
jsonFields: Record<string, true>, // json类型
relationFields: Record<String, true>, // relation类型
},
) => ItemUpdateData;


键入'/'获得帮助

😀

以上设计基于了一个假设,即所有的realtion关联都适用graphql友好的一种格式来组织。如下:

tsx
...
type ExampleUpdateDataWithRelation = {
aRelation:{
id: AIDType;
name: string; // 其他有意义的key
}
}

通用查询接口

当前业务中主要存在两类查询,

一类查询为与table相关的查询

另一类查询为与Select相关的查询

table相关的查询

该类查询的本质上为分页相关的查询,即需要提供如下类型的返回值:

tsx
...
type PaginationResult = {
data: any[];
success: boolean;
totoal: number;
}

其输入参数,为页面的QueryForm的输入值。同理,受gql的要求:

对于查询的值({[key]:value}

对于deletetime字段来说,它仅有如下情况

非空,实际注入值为 {deletetime:{not:{equals:null}}}

空,实际注入值为{deletetime:{equals:null}}

对于string类型,我们需要转换为{[key]:{contains:value}}

对于Ord类型(可比较大小)

比较类型有lt|lte|gt|gte

需要转化为{[key]:{[method]:value}}

对于关联关系,

对单个的关联:需要转化为{[`${key}Id`]:{equals:value}}

对多个的关联:较为复杂(尽可能少的使用)

针对于以上情况,我们需要对query object生成key:

tsx
...
declare const generateConvertQueryObjectKey =
(
type: string,
{
stringFields,
ordFields,
relationFields,
}: {
stringFields: Record<string, true>;
ordFields: Record<string, true>;
relationFields: Record<string, true>;
},
) =>
[(queryObj: any) => string, (key:string)=>boolean];

该函数同时返回用来判断当前所在缓存是否为回收站的。

select相关的查询

select相关的查询通常意义上是通过select的searc字段进行对应字段的查询。本质上,由于并不需要分页,故为了避免大量数据的返回,通常限定仅仅查100条。

与mutation之间的关联

为了达到快速响应的目的,原则上,我们希望所有的mutation操作都可以于当前缓存进行交互。这种交互按使用场景,基本上可以分为以下几类:

字段更新:

单个更新,由于返回id的原因,apollo会为我们自动处理这类更新

选中更新,此时仅仅返回影响的行数,

行数满足,按照id,在字段上,进行手动更新。

行数不满足,进行message提醒,要求用户手动刷新所有。

查询条件更新,此时需要先通过readQuery拿到当前更新的行数

行数满足,对于所有的在字段上,进行手动更新。(这里存疑,可能失败)

行数不满足,进行message提醒,要求用户手动刷新所有。

item删除:按发生场景,又可以细分为:

移动到垃圾桶。

单个,选中:对于id存在的进行删除。(优化:仅仅处理非垃圾桶缓存)

查询条件:对于当前查询对应的storeFieldName所在的数组置空。

从垃圾桶收回

单个,选中:对于id存在的进行删除。(优化:仅仅处理垃圾桶缓存)

查询条件:对于当前查询对应的storeFieldName所在的数组置空。

item新增:由于通常意义上,并不确定当前新增是否可以满足当前查询条件,故此时只能依赖后台返回数据

按照上一次查询条件,重新发起请求。

强制刷新,由于上述情况并不能覆盖所有情况,故在多次复杂操作时,缓存可能更新不及时,此时,需要清空当前查询所对应的storeFieldName,并按照上一次查询条件进行查询。


键入'/'获得帮助

通常来说,强制刷新的场景往往出现在一下场景:

1.

进行条件查询A

2.

进行条件查询B(A,B两次查询之间存在交集a)

3.

变更不在a中的某一项,或者新增。(此时使得更新的项恰好符合查询A)

4.

变更结束,条件查询B符合情况(代码自动更新正确)

5.

返回条件查询A,(代码无法自动更新,导致后端与缓存数据不一致)


键入'/'获得帮助

以上情况存在一些通用逻辑,整理如下:

基于查询条件,生成storeFieldName(即,上一小节的convertQueryObjectKey)

用于判断当前storeFieldName是否在垃圾桶

这些操作,本质上需要返回记录上一次查询的结果。

其它

还有一些需要注意的点:

在回收站关闭时,需要手动刷新当前页。

每次移动回收站时,需要同时刷新回收站当前页。


键入'/'获得帮助


键入'/'获得帮助