GraphQL请求详解(v4)

阅读之前

什么是GraphQL?

Oinone官方解读
GraphQL入门

可视化请求工具

insomnia下载

概述

(以下内容简称GQL)

众所周知,平台的所有功能都是通过一系列元数据定义来驱动的,而GQL作为服务端和客户端交互协议,其重要性不言而喻。下面会从以下几个方面介绍GQL在平台中是如何运作的:

  • 服务端定义元数据生成GQL对应的schema
  • 通过HttpClient发起一个GQL请求
  • 通过window.open使用GET方式发起一个GQL请求
  • 客户端泛化调用任意API服务
  • 客户端通过运行时上下文RuntimeContext发起GQL请求

准备工作

在开始介绍GQL之前,我们需要定义一个可以被GQL请求的服务端函数,以此来介绍我们的相关内容。(对服务端不感兴趣的同学可以跳过代码部分)

DemoModel.java
@Model.model(DemoModel.MODEL_MODEL)
@Model(displayName = "演示模型", labelFields = {"name"})
public class DemoModel extends IdModel {

    private static final long serialVersionUID = -7211802945795866154L;

    public static final String MODEL_MODEL = "demo.DemoModel";

    @Field(displayName = "名称")
    private String name;

    @Field(displayName = "是否启用")
    private Boolean isEnabled;
}
DemoModelAction.java
@Model.model(DemoModel.MODEL_MODEL)
public class DemoModelAction {

    @Action(displayName = "启用")
    public DemoModel enable(DemoModel data) {
        data.setIsEnabled(true);
        data.updateById();
        return data;
    }

    @Action(displayName = "禁用")
    public DemoModel disable(DemoModel data) {
        data.setIsEnabled(false);
        data.updateById();
        return data;
    }
}

上面的java代码定义了演示模型字段动作

  • 字段 field
    • id:ID 整数 Integer
    • name:名称 字符串 String
    • isEnabled:是否启用 布尔 Boolean
  • 动作 action
    • enable:启用 提交动作 ServerAction
    • disable:禁用 提交动作 ServerAction

服务端定义元数据生成GQL对应的schema

模型和字段
type DemoModel {
    id: Long
    name: String
    isEnabled: Boolean
}
动作
type DemoModelInput {
    id: Long
    name: String
    isEnabled: Boolean
}

type DemoModelQuery {
    queryOne(query: DemoModelInput): DemoModel
    ......
}

type DemoModelMutation {
    enable(data: DemoModelInput): DemoModel
    disable(data: DemoModelInput): DemoModel
    ......
}

PS:平台内置了多种QueryMutation定义,通过模型继承关系将自动生成,无需手动定义。比如Query定义包括queryOnequeryPage等;Mutation定义包括createupdate等。特殊情况下,默认逻辑无法满足时,服务端通常采用函数重载的方式进行替换,客户端则无需关心。

生成规则

  • type DemoModel:通过模型编码demo.DemoModel.分隔后的最后一位,并转换为大驼峰格式。字段与声明类型一致。
  • type DemoModelInput动作入参定义,未做特殊声明的情况下与模型定义一致。
  • type DemoModelQuerytype DemoModelMutationQueryMutation为固定后缀,分别生成动作相关类型。当函数类型为QUERY时,使用Query后缀,其他情况使用Mutation后缀。

(此处仅做简单解释,详细生成规则过于复杂,客户端无需关心)

Query类型的GQL示例

query {
  demoModelQuery {
    queryOne(query: { id: ${id} }) {
      id
      name
      isEnabled
    }
  }
}

Mutation类型的GQL示例

mutation {
  demoModelMutation {
    enable(data: { id: ${id} }) {
      id
      name
      isEnabled
    }
  }
}

GQL请求规则

  • querymutationGQL默认关键字,若为query则可缺省。
  • demoModelMutation:通过DemoModelMutation类型定义转换为小驼峰获取。Query类似。
  • enable:通过服务端定义的函数名称获取,以上示例中函数名称Java函数名称一致,特殊情况下,服务端可使用@Function(name = "")注解或@Action.Advanced(name = "")注解进行修改。disable类似。
  • (data: { id: ${id} })data通过服务端定义的参数名称获取,与Java函数参数名称一致。值类型与服务端声明类型一致,对象类型采用{}包裹,对象中的每个属性类型与服务端声明类型同样一致。此处服务端声明的data名称的入参仅包含DemoModelInput中定义的属性。GQL是通过参数名称进行参数映射的,与参数位置无关。
  • 响应字段声明:idnameisEnabled为本次请求后获取到的相关字段,不在响应字段声明中的属性无法被获取。

PS:GQL在客户端发起的请求中提供了VariablesFragmentInline Fragment等方式进行GQL的复用。感兴趣的同学可在GQL官网自行学习。

TypeScript最佳实践

通常情况下,我们是不需要手动发起GQL的,但在某些特殊场景(无法获取元数据上下文等)中,这是无法避免的。

在我们学习如何发起GQL请求之前,我们需要达成一个基本共识,即ts类型声明API服务归集

  • ts类型声明:需按照模型定义类型声明,通常我们将这些类型声明放在typing.ts文件中进行管理。
  • API服务归集:需按照模型对应的服务分别定义相关API服务的调用方法,并将这些服务放在service.ts文件中进行管理。具体的方法根据实际情况进行判断,但不建议使用复杂入参定义服务方法。
  • 模块名称归集:需将所有API中用到的模块名称统一放在module-name.ts文件中进行管理。

工程结构

service目录将为该工程提供全部的API服务。

这是我们建议的工程结构,具体工程结构也可以根据自身情况进行相应变更。接下来,我们会在通过HttpClient发起一个GQL请求章节提供相关的示例代码作为参考。

├── src
│   ├── service
│   │   ├── demo-model
│   │   │   ├── index.ts
│   │   │   ├── service.ts
│   │   │   └── typing.ts
│   │   ├── model.ts
│   │   ├── module-name.ts
│   │   └── index.ts
│   ├── main.ts
│   └── shim-vue.d.ts
├── tsconfig.json
└── vue.config.js

通过HttpClient发起一个GQL请求

service/module-name.ts
export const DEMO = "demo";
service/index.ts
export * from './demo-model';
service/demo-model/typing.ts
export interface DemoModel {
    id?: string;
    name?: string;
    isEnabled?: string;
}
service/demo-model/service.ts
import { HttpClient } from '@kunlun/dependencies';
import { DEMO } from '../module-name';
import { DemoModel } from './typing';

const http = HttpClient.getInstance();

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    const gql = `mutation {
  demoModelMutation {
    enable(data: {id: "${id}"}) {
      id
      name
      isEnabled
    }
  }
}`;
    const res = await http.mutate<DemoModel>(DEMO, gql);
    return res.data.demoModelMutation.enable;
  }
}
service/demo-model/index.ts
export * from './typing';
export * from './service';

PS:对于GQL字符串的构造,平台还提供了另一种构造方式,开发者可根据自身喜好进行选择。

service/model.ts(模型名称归集,与模块名称归集类似)
export const DEMO_MODEL_NAME = 'demoModel';
service/demo-model/service.ts
import { GQL } from '@kunlun/dependencies';
import { DEMO_MODEL_NAME } from '../model';
import { DEMO } from '../module-name';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    return GQL.mutation(DEMO_MODEL_NAME, 'enable')
      .buildRequest((builder) => builder.buildObjectParameter('data', (builder) => builder.stringParameter('id', id)))
      .buildResponse((builder) => builder.parameter('id', 'name', 'isEnabled'))
      .request(DEMO);
  }
}
使用对应服务
import { DemoService } from './service';

// 1. async/await (推荐)
const res = await DemoService.enable('1');
console.log(res);

// 2. Promise#then
DemoService.enable('1').then((res) => {
    console.log(res);
});

至此,我们已经可以发起任何GQL请求了。

通过window.open使用GET方式发起一个GQL请求

使用HttpClient发起的请求不论是使用query还是mutate,都是POST方式的请求。对于文件下载等需要使用GET方式的请求,我们通常使用window.open方法,并打开新的浏览器窗口来实现这一功能。

const gql = ${GQL};

window.open(UrlHelper.appendBasePath(/pamirs/${MODULE_NAME}?query=${encodeURIComponent(gql)}), '_blank');

客户端泛化调用任意API服务

GQL中定义的模型字段过多时,我们往往会发觉直接手写一个GQL的难度是相当高的,为此,我们提供了一个较为简单的调用服务方式,以辅助生成我们所需的GQL

该方法采用动态获取元数据的方式实现的泛化调用,仅限于开发环境使用,不适合生产环境使用。获取元数据的相关API服务相对于业务来说是一些无意义的损耗,因此,我们通常建议在开发时通过该方式生成GQL后,从请求体中拷贝该GQL,并改为通过HttpClient方式进行调用。一般而言,生成一次的GQL后,在未来的变更中仅会有个别字段的变化,不会产生过大的变化。

service/model.ts(模型名称归集,与模块名称归集类似)
export const DEMO_MODEL = 'demo.DemoModel';
service/demo-model/service.ts
import { GenericFunctionService } from '@kunlun/dependencies';
import { DEMO_MODEL } from '../model';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    return GenericFunctionService.INSTANCE.executeByName(DEMO_MODEL, 'enable', { deep: 1 }, { id });
  }
}

这种方式需要使用模型编码函数名称作为入参,deep参数控制响应字段声明的层级,最后的不定长参数作为函数的入参。

由于泛化调用服务会动态获取模型关联模型函数等相关信息,函数名称参数名称等信息无法提前预知,也就无法明确定义参数名称,因此需要根据参数位置进行入参匹配。

小结

以上两种方式均是在没有元数据的情况下使用的请求方式,从之前的文章中我们可以了解到,元数据仅在main-view组件中才进行了收集,其他的情况是无法正常获取元数据的。

比如我们通常在mask中定义的组件,app-switchermenu等,这些组件需要发起相关请求,但其元数据无法在页面中获取。

不仅如此,这种请求方式只有在业务场景确定模型不再发生变更,并且确定该服务与权限相关功能完全无关时才可以使用。

由于我们无法提前预知该服务的相关变化,这些问题是无法避免的:

  • 当模型字段发生变化时,在代码中定义的GQL也需要发生相应的变更。
  • 当权限限制该用户发起该请求时,请求结果只会报错,我们无法预知服务是否可以正常调用。

客户端通过运行时上下文RuntimeContext发起GQL请求

在可以获取到元数据的情况下,我们可以用下面这种方式发起GQL请求。

service/demo-model/service.ts
import {
  GenericFunctionService,
  isRuntimeServerAction,
  RuntimeContext,
  RuntimeServerAction
} from '@kunlun/dependencies';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(runtimeContext: RuntimeContext, id: string): Promise<DemoModel | undefined> {
    const action = runtimeContext.model.modelActions.find(
      (v) => isRuntimeServerAction(v) && v.functionDefinition?.name === 'enable'
    ) as RuntimeServerAction;

    const functionDefinition = action?.functionDefinition;

    if (!functionDefinition) {
      return;
    }

    const requestModelFields = runtimeContext.getRequestModelFields();

    return GenericFunctionService.INSTANCE.execute(
      functionDefinition,
      {
        model: runtimeContext.model,
        requestFields: requestModelFields
      },
      { id }
    );
  }
}

在这个示例中,我们通过runtimeContext.model.modelActions查找所需调用的动作,如果该动作在当前运行时上下文中未找到,那我们就能知道这个动作可能在当前页面中未配置无权限访问。并且从运行时上下文中获取到的请求模型字段,可以完全按照页面配置进行按需请求。这也是我们推荐使用的调用方式之一。

需要注意的是,在GenericFunctionService#execute可选项中requestFields属性优先于deep属性。

Oinone社区 作者:数式-海波原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/45.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(0)
数式-海波的头像数式-海波数式管理员
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

  • 前端视图的元数据与数据的传递、交互

    在阅读本篇文章之前,您需要学习以下知识点: 1: 元数据 视图的元数据 在日常开发中,我们会经常遇到自定义的字段、动作、视图需要界面设计器配置的数据,这些数据可能是当前页面的字段,也有可能动作,那么如何获取呢? 视图元数据分为两种:1: 当前视图(metadataRuntimeContext)2: 根视图(rootRuntimeContext) 那么这两种类型怎么区分呢? 举个例子:1: 如果当前字段是在表单中,那么当前视图就是表单,根视图就表单的父级视图,如果只有一个表单视图,那么当前视图就是根视图。2: 如果当前视图是表单,但是表单里面有个表格,对于表格字段而言,当前视图就是表格,根视图就是表单。 当前视图的元数据(metadataRuntimeContext) 在前端,我们通过 metadataRuntimeContext 来获取视图的元数据,例如: export class CustomFormStringFieldSingleWidget extends FormStringFieldSingleWidget { protected mounted(): void { console.log(this.metadataRuntimeContext); } /** * 界面设计器配置的动作 */ @Widget.Reactive() protected get modelActions() { return this.metadataRuntimeContext.model.modelActions } /** * 界面设计器配置的字段 */ @Widget.Reactive() protected get modelFields() { return this.metadataRuntimeContext.model.modelFields } } 属性名 类型 可选性 描述 viewAction RuntimeViewAction 是 运行时跳转动作(通过跳转动作创建的运行时上下文具备该属性) module RuntimeModule 否 运行时模块 model RuntimeModel 否 运行时模型 view RuntimeView 否 运行时视图 viewLayout DslDefinition \| undefined 否 视图布局 DSL,从运行时视图解析获得 viewDsl DslDefinition \| undefined 否 视图模板 DSL,从运行时视图解析获得 viewTemplate DslDefinition 否 视图最终执行 DSL,从运行时视图解析获得或根据布局 DSL 和模板 DSL 合并生成 getModel (model: string, isBelong?: boolean) => GetModelResult \| undefined 否 获取模型,返回获取的模型和所在的运行时上下文 getModelField (data: string, isBelong?: boolean) => GetModelFieldResult \| undefined 否 获取模型字段,返回获取的模型字段和所在的运行时上下文 getRequestModelFields (options?: GetRequestModelFieldsOptions) => RequestModelField[] 否 获取请求字段 getDefaultValue () => Promise<Record<string, unknown>> 否 获取默认值 getInitialValue () => Promise<Record<string, unknown>> 否 获取初始值 运行时模型(model) 属性名 类型 可选性 描述 id string 是 模型 id model string 否 模型编码 name string 否 技术名称 modelFields RuntimeModelField[] 否 模型字段 modelActions RuntimeAction[] 否 模型动作 type ModelType 是 模型类型 module string 是 模块编码 moduleName string 否 模块名称 moduleDefinition RuntimeModule 是…

    2024年10月8日
    90800
  • oio-grid 栅格

    24 栅格系统。 <oio-row :gutter="24"> <oio-col :span="12"></oio-col> <oio-col :span="12"></oio-col> </oio-row> 概述 布局的栅格化系统,我们是基于行(row)和列(col)来定义信息区块的外部框架,以保证页面的每个区域能够稳健地排布起来。下面简单介绍一下它的工作原理: 通过\row\在水平方向建立一组\column\(简写 col) 你的内容应当放置于\col\内,并且,只有\col\可以作为\row\的直接元素 栅格系统中的列是指 1 到 24 的值来表示其跨越的范围。例如,三个等宽的列可以使用 \<a-col :span="8" />\ 来创建 如果一个\row\中的\col\总和超过 24,那么多余的\col\会作为一个整体另起一行排列 Flex 布局 我们的栅格化系统支持 Flex 布局,允许子元素在父节点内的水平对齐方式 – 居左、居中、居右、等宽排列、分散排列。子元素与子元素之间,支持顶部对齐、垂直居中对齐、底部对齐的方式。同时,支持使用 order 来定义元素的排列顺序。 Flex 布局是基于 24 栅格来定义每一个『盒子』的宽度,但不拘泥于栅格。 API Row 成员 说明 类型 默认值 align flex 布局下的垂直对齐方式:top middle bottom string top gutter 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 [水平间距, 垂直间距](1.5.0 后支持)。 number/object/array 0 justify flex 布局下的水平排列方式:start end center space-around space-between string start wrap 是否自动换行 boolean false Col 成员 说明 类型 默认值 版本 flex flex 布局填充 string|number – offset 栅格左侧的间隔格数,间隔内不可以有栅格 number 0 order 栅格顺序,flex 布局模式下有效 number 0 pull 栅格向左移动格数 number 0 push 栅格向右移动格数 number 0 span 栅格占位格数,为 0 时相当于 display: none number – xxxl ≥2000px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – xs <576px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – sm ≥576px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – md ≥768px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – lg ≥992px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – xl ≥1200px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object – xxl ≥1600px 响应式栅格,可为栅格数或一个包含其他属性的对象 number|object –

    2023年12月18日
    51000
  • 组件数据交互基础(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4) 了解组件相关内容。 Class Component(ts)(v4) 组件生命周期(v4) 组件数据交互概述 数据结构设计 数据结构分为三大类,列表(List)、对象(Object)以及弹出层(Popup)。 列表(List):用于多条数据的展示,主要包括搜索(用户端)、自定义条件(产品端)、排序、分页、数据选中、数据提交、数据校验功能。 对象(Object):用于单条数据的展示,主要包括数据提交、数据校验功能。 弹出层(Popup):用于在一块独立的空间展示对应类型的数据。 数据结构与对于的内置视图类型: 列表(List):表格视图(TABLE)、画廊视图(GALLERY) 对象(Object):表单视图(FORM)、详情视图(DETAIL) 数据交互设计原则 组件与组件之间的结构关系是独立的,组件与组件之间的数据是关联的。因此,数据交互整体采用“作用域隔离(View),行为互通(CallChaining),数据互通(ActiveRecords)”这样的基本原则进行设计。实现时,围绕不同视图类型定义了一类数据结构所需的基本属性。 在弹出层进行设计时,使用Submetadata的方式,将包括弹出层在内的所有组件包含在内,以形成新的作用域。 通用属性及方法 属性 rootData:根数据源 dataSource:当前数据源 activeRecords:当前激活数据源 为了在实现时包含所有数据结构,我们统一采用ActiveRecord[]类型为所有数据源的类型,这些数据源在不同类型的视图中表示不同的含义: 列表(List):dataSource为列表当前数据源,activeRecords为列表中被选中的数据。特别的,showDataSource为当前展示的数据源,它是dataSource经过搜索、排序、分页等处理后的数据源,也是我们在组件中真正使用的数据源。 对象(Object):daraSource和activeRecords总是完全一致的,且长度永远为1。因此我们有时也在组件中定义formData属性,并提供默认实现:this.activeRecords?.[0] || {}。 方法 reloadDataSource:重载当前数据源 reloadActiveRecords:重载当前激活数据源 由于底层实现并不能正确判断当前使用的数据类型,因此我们无法采用统一标准的数据源修改方法,这时候需要开发者们自行判断。 重载列表数据源 cosnt dataSource: ActiveRecord[] = 新的数据源 // 重载数据源 this.reloadDataSource(dataSource); // 重置选中行 this.reloadActiveRecords([]); 重载表单数据源 cosnt dataSource: ActiveRecord[] = 新的数据源(数组中有且仅有一项) // 重载数据源 this.reloadDataSource(dataSource); // 此处必须保持相同引用 this.reloadActiveRecords(dataSource); 内置CallChaining(链式调用) 在自动化渲染过程中,我们通常无法明确知道当前组件与子组件交互的具体情况,甚至我们在定义当前组件时,并不需要关心(某些情况下可能无法关心)子组件的具体情况。这也决定了我们无法在任何一个组件中完整定义所需的一切功能。 为了保证组件行为的一致性,我们需要某些行为在各个组件的实现需要做到组件自治。以表格视图为例:当view标签在挂载时,我们无法确定应该怎样正确的加载数据,因此,我们需要交给一个具体的element标签来完成这一功能。当element标签对应的组件发生变化时,只需按照既定的重载方式将数据源提交给view标签即可。 除了保证组件行为的一致性外,我们不能完全的信任第三方框架对组件生命周期的处理顺序。因此,我们还需要对组件行为进行进一步的有序处理。以表格视图为例:我们希望搜索视图(SEARCH)的处理总是在加载数据前就处理完成的,这样将可以保证我们加载数据可以正确处理搜索条件,而这一特性并不随着模板结构的变化而发生变化。 平台内置的CallChaining mountedCallChaining:挂载时钩子; refreshCallChaining:刷新时钩子; submitCallChaining:提交时钩子; validatorCallChaining:验证时钩子; 优先级常量 VIEW_WIDGET_PRIORITY(0):视图组件优先级 FETCH_DATA_WIDGET_PRIORITY(100):数据提供者组件优先级 SUBVIEW_WIDGET_PRIORITY(200):子视图组件优先级 未设置优先级的hook将最后执行,在通常情况下,你无需关心优先级的问题。 注意事项 CallChaining通常不需要手动初始化,仅需通过inject方式获取即可。 CallChaining的hook/unhook方法需要在组件生命周期的mounted/unmounted分别执行,如无特殊情况,一般通过this.path作为挂载钩子的唯一键。 在字段组件中使用mountedCallChaing @Widget.Reactive() @Widget.Inject() protected mountedCallChaining: CallChaining | undefined; protected mountedProcess() { // 挂载时处理 } protected mounted() { super.mounted(); this.mountedCallChaining?.hook(this.path, () => { this.mountedProcess(); }); } protected unmounted() { super.unmounted(); this.mountedCallChaining?.unhook(this.path); } 字段组件的mountedCallChaing并不是必须的,因此我们未进行内置处理。 一般的,当视图数据被加载完成时,字段组件的formData和value等属性,将通过响应式自动获取对应的值,因此在大多数情况下是不需要使用这一特性的。 当我们需要对字段获取的值做进一步初始化处理时,我们将需要使用这一特性。例如TreeSelect组件,必须在初始化时填充树下拉所需的结构化数据,这样才能正确展示对应的值。 当字段组件的mounted方法被执行时,我们还未执行视图数据加载,因此,在我们无法在mounted方法中操作formData和value等属性,只有在mountedCallChaining被view标签执行时,按照执行顺序,此时字段的mountedChaining将在视图数据被加载完成后执行。 数据源持有者和数据源提供者 在设计上,我们通常将view标签设计为数据源持有者,将element标签设计为数据源提供者。 原则上,在一个视图中有且仅有一个数据源提供者。 即:当一个element标签的实现组件通过reloadDataSource方法向view标签设置数据源,我们就称该实现组件为当前view标签的数据源提供者,view标签为数据源持有者。 provider/inject 阅读该章节需要理解vue的依赖注入原理 在实现上,我们通过provider/inject机制将上述通用属性/方法进行交替处理,就可以实现根据模板定义的结构进行隔离和共享功能。 例如dataSource属性的实现: /** * 上级数据源 * @protected */ @Widget.Reactive() @Widget.Inject('dataSource') protected parentDataSource: ActiveRecord[] | undefined; /** * 当前数据源 * @protected */ @Widget.Reactive() private currentDataSource: ActiveRecord[] | null | undefined; /** * 提供给下级的数据源 * @protected */ @Widget.Reactive() @Widget.Provide() public get dataSource(): ActiveRecord[] | undefined { return this.currentDataSource || this.parentDataSource; } 不足的是,由于provider/inject机制特性决定,通过provider提供的属性和方法,在某些情况下可能会进行穿透,导致组件通过inject获取的属性和方法并非是我们所期望的那样,因此,我们仍然需要进行一些特殊的处理,才能正确的处理子视图的数据交互,这一点在对象(Object)类型的视图中会详细介绍。 运行时上下文(metadataHandle/rootHandle) 在之前的文章中,我们知道前端的字段/动作组件渲染和后端元数据之间是密不可分的。 在数据交互方面,后端元数据对于字段类型的定义,将决定从API接口中获取的字段、数据类型和格式,以及通过API接口提交数据到后端时的字段、数据类型和格式。 数据类型和格式可以通过field标签的data属性,获取到经过后端编译的相关字段元数据。 那么,我们该如何决定,当数据提供者向后端发起请求时,应该获取哪些字段呢? 因此,我们设计了RuntimeContext机制,并通过metadataHandle/rootHandle机制,在任何一个组件都可以通过view标签正确获取已经实现隔离的运行时上下文机制。 以表单视图为例,我们来看这样一个经过合并后的完整视图模板: (以下模板所展示的并非实际的运行时的结果,而是为了方便描述所提供的完整视图模板) <view type="FORM"> <element widget="actions"> <action…

    2023年11月1日
    73100
  • 如何自定义 GraphQL 请求

    在开发过程中,有时需要自定义 GraphQL 请求来实现更灵活的数据查询和操作。本文将介绍两种主要的自定义 GraphQL 请求方式:手写 GraphQL 请求和调用平台 API。 方式一:手写 GraphQL 请求 手写 GraphQL 请求是一种直接编写查询或变更语句的方式,适用于更复杂或特定的业务需求。以下分别是 query 和 mutation 请求的示例。 1. 手写 Query 请求 以下是一个自定义 query 请求的示例,用于查询某个资源的语言信息列表。 const customQuery = async () => { const query = `{ resourceLangQuery { queryListByEntity(query: {active: ACTIVE, installState: true}) { id name active installState code isoCode } } }`; const result = await http.query('resource', query); this.list = result.data['resourceLangQuery']['queryListByEntity']; }; 说明: query 语句定义了一个请求,查询 resourceLangQuery 下的语言信息。 查询的条件是 active 和 installState,只返回符合条件的结果。 查询结果包括 id、name、active、installState 等字段。 2. 手写 Mutation 请求 以下是一个 mutation 请求的示例,用于创建新的资源分类信息。 const customMutation = async () => { const code = Date.now() const name = `测试${code}` const mutation = `mutation { resourceTaxKindMutation { create(data: {code: "${code}", name: "${name}"}) { id code name createDate writeDate createUid writeUid } } }`; const res = await http.mutate('resource', mutation); console.log(res); }; 说明: mutation 语句用于创建一个新的资源分类。 create 操作的参数是一个对象,包含 code 和 name 字段。 返回值包括 id、createDate 等字段。 方式二:调用平台的 API 平台 API 提供了简化的 GraphQL 调用方法,可以通过封装的函数来发送 query 和 mutation 请求。这种方式减少了手写 GraphQL 语句的复杂性,更加简洁和易于维护。 1. 调用平台的 Mutation API 使用平台的 customMutation 方法可以简化 Mutation 请求。 /** * 自定义请求方法 * @param modelModel 模型编码 * @param method 方法名或方法对象 * @param records 请求参数,可以是单体对象或者对象的列表 * @param requestFields…

    2024年9月21日
    84300
  • 【前端】登录页面扩展点

    登录页面扩展点 场景 1: 登录之前需要二次确认框2: 前端默认的错误提示允许被修改3: 后端返回的错误提示允许被修改4: 登录后跳转到自定义的页面 方案 前端默认错误可枚举 errorMessages: { loginEmpty: '用户名不能为空', passwordEmpty: '密码不能为空', picCodeEmpty: '图形验证码不能为空', phoneEmpty: '手机号不能为空', verificationCodeEmpty: '验证码不能为空', picCodeError: '图形验证码错误', inputVerificationCodeAlign: '请重新输入验证码' } 登录按钮添加拓展点beforeClick、afterClick 代码 新增一个ts文件,继承平台默认的LoginPageWidget @SPI.ClassFactory(RouterWidget.Token({ widget: 'Login' })) export class CustomLoginPageWidget extends LoginPageWidget { constructor() { super(); // 修改前端默认的错误文案 this.errorMessages.loginEmpty = '登录用户名不能为空'; } /** * 用来处理点击「登录」之前的事件,可以做二次确定或者其他的逻辑 * 只有return true,才会继续往下执行 */ public beforeClick(): Promise<Boolean | null | undefined> { return new Promise((resolve) => { Modal.confirm({ title: '提示', content: '是否登录?', onOk: () => { resolve(true); } }); }); } /** * * @param result 后端接口返回的数据 * * 用来处理「登录」接口调用后的逻辑,可以修改后端返回的错误文案,也可以自定义 * * 只有return true,才会执行默认的跳转事件 */ public afterClick(result): Promise<any | null | undefined> { // if(result.redirect) { // 自定义跳转 //return false //} if (result.errorCode === 20060023) { result.errorMsg = '手机号不对,请联系管理员'; } return result; } }

    2023年11月1日
    53700

Leave a Reply

登录后才能评论