组件数据交互基础(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):daraSourceactiveRecords总是完全一致的,且长度永远为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并不是必须的,因此我们未进行内置处理。

一般的,当视图数据被加载完成时,字段组件formDatavalue等属性,将通过响应式自动获取对应的值,因此在大多数情况下是不需要使用这一特性的。

当我们需要对字段获取的值做进一步初始化处理时,我们将需要使用这一特性。例如TreeSelect组件,必须在初始化时填充树下拉所需的结构化数据,这样才能正确展示对应的值。

字段组件mounted方法被执行时,我们还未执行视图数据加载,因此,在我们无法在mounted方法中操作formDatavalue等属性,只有在mountedCallChainingview标签执行时,按照执行顺序,此时字段的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 name="create" invisible="activeRecord.id" />
        <action name="update" invisible="!activeRecord.id" />
    </element>
    <element widget="fields">
        <field data="id" invisible="true" />
        <field data="code" />
        <field data="name" />
        <field data="relationOne" widget="Form">
            <view type="FORM">
                <element widget="form">
                    <field data="id" invisible="true" />
                    <field data="code" />
                    <field data="name" />
                </element>
            </view>
        </field>
        <field data="relationMany" widget="Table">
            <view type="TABLE">
                <element widget="actionBar">
                    <action name="openCreateDialog">
                        <view type="FORM">
                            <element widget="fields">
                                <field data="id" invisible="true" />
                                <field data="code" />
                                <field data="name" />
                            </element>
                        </view>
                    </action>
                </element>
                <element widget="table">
                    <field data="id" invisible="true" />
                    <field data="code" />
                    <field data="name" />
                    <element widget="rowActions">
                        <action name="openEditDialog">
                            <view type="FORM">
                                <element widget="fields">
                                    <field data="id" invisible="true" />
                                    <field data="code" />
                                    <field data="name" />
                                </element>
                            </view>
                        </action>
                    </element>
                </element>
            </view>
        </field>
    </element>
</view>

其中:

  • relationOne字段:代表M2O字段,它使用Form组件将字段展开为子表单视图。(O2O类型基本一致)
  • relationMany字段:代表O2M字段,它使用Table组件将字段展开为子表格视图。(M2M类型基本一致)
    • openCreateDialog:代表打开一个创建弹窗,它是一个上下文类型为上下文无关的跳转动作。
    • openEditDialog:代表打开一个编辑弹窗,它是一个上下文类型为单行的跳转动作。

PS:这里我们关注的是组件之间的数据交互,至于动作相关内容,我们会在其他文章中做进一步介绍。在这里我们只需要了解,当按钮的上下文类型为上下文无关时,该按钮将不会携带任何数据到下一个视图中;当按钮的上下文类型为单行时,该按钮将携带activeRecords[0]的数据到下一个视图中。

通过这个视图模板,我们将构建出如下图所示的RuntimeContext结构:

image.png

``` mermaid
graph TD
metadataHandle-1("metadataHandle-1(ViewAction)") ---> rootHandle-1("rootHandle-1(view type=#quot;FORM#quot;)")

rootHandle-1 ---> metadataHandle-2 & metadataHandle-3

metadataHandle-2("metadataHandle-2(field data=#quot;relationOne#quot;)") ---> rootHandle-2("rootHandle-2(view type=#quot;FORM#quot;)")
metadataHandle-3("metadataHandle-3(field data=#quot;relationMany#quot;)") ---> rootHandle-3("rootHandle-3(view type=#quot;TABLE#quot;)")

rootHandle-3 ---> metadataHandle-4 & metadataHandle-5

metadataHandle-4("metadataHandle-4(action name=#quot;openCreateDialog#quot;)") ---> rootHandle-4("rootHandle-4(view type=#quot;FORM#quot;)")
metadataHandle-5("metadataHandle-5(action name=#quot;openEditDialog#quot;)") ---> rootHandle-5("rootHandle-5(view type=#quot;FORM#quot;)")
```

由此可见,我们的metadataHandle可能通过页面入口(ViewAction)提供,也可能通过字段(field标签)提供,还可能通过动作(action标签)提供。无一例外的是,所有的view标签都将生成一个与之对应的rootHandle

在编码过程中,我们可以通过this.metadataRuntimeContextthis.rootRuntimeContext获取到对应的运行时上下文。前端运行时上下文API文档

列表(List)数据交互

对于列表(List)数据结构来说,我们目前提供了两种具体的视图:表格视图(TABLE)画廊视图(GALLERY),这两种视图分别对应不同的数据展示形态。

  • 表格视图(TABLE):字段代表表格中的列(Column),每一列的数据使用相同方式展示,无法使用布局特性。
  • 画廊视图(GALLERY):使用list - item形式进行渲染,每一个item可以通过DSL描述其布局特性。

特别的是,搜索视图(SEARCH)是对列表(List)数据结构提供搜索(用户端)相关功能的子视图,其独立存在于页面中是没有意义的。

默认表格视图(TABLE)

<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" />
        </view>
    </pack>
    <pack widget="group" slot="tableGroup">
        <element widget="actionBar" slot="actionBar">
            <xslot name="actions" />
        </element>
        <element widget="table" slot="table">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" />
            <element widget="rowActions" slot="rowActions" />
        </element>
    </pack>
</view>

在之前的文章中,我们了解了布局和标签的基本概念,为了能更好的说明数据交互相关的特征,我们将上述视图进行简化,仅留下与数据交互相关的标签。

<view type="TABLE">
    <view type="SEARCH">
        <element widget="search" slot="search" />
    </view>
    <element widget="actionBar" slot="actionBar">
        <xslot name="actions" />
    </element>
    <element widget="table" slot="table">
        <xslot name="fields" />
        <element widget="rowActions" slot="rowActions" />
    </element>
</view>

在这个视图中,我们可以获得如下信息:

  • 主视图为表格(TABLE)视图。
  • 包含一个搜索(SEARCH)子视图。
  • 包含两个主要的element组件widget="actionBar"widget="table"
  • widget="table"中内置了一个element组件widget="rowActions"

在我们内置的组件实现中,widget="table"这个组件通过调用后端接口,为整个视图提供数据源。

组件挂载时序图

image.png

``` mermaid
sequenceDiagram

participant TableView as TableView("view type=#quot;TABLE#quot;")

participant SearchView as SearchView("view type=#quot;SEARCH#quot;")
participant Search as SearchWidget("element widget=#quot;search#quot;")

participant ActionBar as ActionBarWidget("element widget=#quot;actionBar#quot;")

participant Table as TableWidget("element widget=#quot;table#quot;")
participant RowActionBar as RowActionBarWidget("element widget=#quot;rowActions#quot;")

TableView ->> TableView : before mount
TableView ->> SearchView : before mount
SearchView ->> Search : before mount
TableView ->> ActionBar : before mount
TableView ->> Table : before mount
Table ->> RowActionBar : before mount

Search ->> SearchView : mounted
SearchView ->> TableView : mounted
ActionBar ->> TableView : mounted
RowActionBar ->> Table : mounted
Table ->> TableView : mounted
TableView ->> TableView : mounted

TableView ->> TableView : mountedCallChaing hook (VIEW_WIDGET_PRIORITY)

TableView ->> SearchView : mountedCallChaing hook (VIEW_WIDGET_PRIORITY)
SearchView ->> Search : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)

TableView ->> Table : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)

```

在上图中,我们可以看到,mountedCallChaining是在所有组件全部渲染并挂载完成后,通过最上层的view标签进行调用的,并且按照一定的优先级顺序执行了每个组件实现的挂载钩子函数

组件数据源加载

widget="search"组件执行了挂载钩子函数时,将从url参数中获取所需的searchBodysearchCondition属性,并通过flushSearchParameters方法将这两个参数提交到表格视图(TABLE)

widget="table"组件执行了挂载钩子函数时,将执行加载数据相关功能,并通过reloadDataSourcereloadActiveRecords方法对数据源进行向上提交。

image.png

``` mermaid
sequenceDiagram

participant TableView as TableView("view type=#quot;TABLE#quot;")

participant SearchView as SearchView("view type=#quot;SEARCH#quot;")
participant Search as SearchWidget("element widget=#quot;search#quot;")

participant Table as TableWidget("element widget=#quot;table#quot;")

TableView ->> SearchView : mountedCallChaing hook (VIEW_WIDGET_PRIORITY)
SearchView ->> Search : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)
Search ->> Search : resolve url parameters
Search ->> SearchView : reloadActiveRecords
Search ->> TableView : flushSearchParameters

TableView ->> Table : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)
Table ->> Table : mounted process (call api)
Table ->> TableView : reloadDataSource
Table ->> TableView : reloadActiveRecords

```

用户行为触发的数据交互

这里列举了一些列表(List)常用的数据交互时序图,用于帮助大家对数据交互的整体逻辑可以有更清晰的理解。

点击搜索

image.png

``` mermaid
sequenceDiagram

participant TableView as TableView("view type=#quot;TABLE#quot;")

participant SearchView as SearchView("view type=#quot;SEARCH#quot;")
participant Search as SearchWidget("element widget=#quot;search#quot;")

participant Table as TableWidget("element widget=#quot;table#quot;")

Search ->> TableView : flushSearchParameters
Search ->> SearchView : refreshCallChaining (call)
SearchView ->> TableView : refreshCallChaining (call)

TableView ->> Table : refreshCallChaining hook (FETCH_DATA_WIDGET_PRIORITY)
Table ->> Table : refresh process (call api)
Table ->> TableView : reloadDataSource
Table ->> TableView : reloadActiveRecords

```

排序/分页

image.png

``` mermaid
sequenceDiagram

participant TableView as TableView("view type=#quot;TABLE#quot;")

participant Table as TableWidget("element widget=#quot;table#quot;")

Table ->> Table : refresh process (call api)
Table ->> TableView : reloadDataSource
Table ->> TableView : reloadActiveRecords

```

表格勾选(table checkbox/radio)

image.png

``` mermaid
sequenceDiagram

participant TableView as TableView("view type=#quot;TABLE#quot;")

participant Table as TableWidget("element widget=#quot;table#quot;")

Table ->> TableView : reloadActiveRecords

```

对象(Object)数据交互

对于对象(Object)数据结构来说,我们目前提供了两种具体的视图:表单视图(FORM)详情视图(DETAIL),这两种视图分别对应不同的数据展示形态。

  • 表单视图(FORM):主要用于数据的填写和提交;可以通过DSL描述其布局特性。
  • 详情视图(DETAIL):主要用于数据的展示,所有字段强制为只读态(与表单的只读态有所不同);可以通过DSL描述其布局特性。

特别的是,表单视图(FORM)详情视图(DETAIL)在对子视图的处理上并不完全相同。主要体现在数据提交类型(submitType)关联关系更新类型(relationUpdateType)这两个属性的处理上。

数据提交类型(submitType)
/**
 * 数据提交类型
 */
export enum SubmitType {
  /**
   * <h3>默认</h3>
   * <p>
   * 基础字段使用全量提交,关联关系字段使用关联关系提交
   * </p>
   */
  default = 'default',
  /**
   * 不提交
   */
  none = 'none',
  /**
   * 关联关系字段关系提交(仅对关联关系字段有效)
   */
  relation = 'relation',
  /**
   * 关联关系字段增量提交(仅对关联关系字段有效)
   */
  increment = 'increment',
  /**
   * 全量提交
   */
  all = 'all'
}
关联关系更新类型(relationUpdateType)
/**
 * 关联关系数据更新类型
 */
export enum RelationUpdateType {
  /**
   * <h3>默认</h3>
   * <p>
   * 默认根据数据提交类型进行自动识别
   * </p>
   */
  default = 'default',
  /**
   * 使用特定接口进行差量更新(关联关系字段强制使用增量提交)
   */
  diff = 'diff',
  /**
   * 使用batch接口进行差量更新(关联关系字段强制使用增量提交)
   */
  batch = 'batch',
  /**
   * 全量更新
   */
  all = 'all'
}

下面分别列举了一些字段组件在不同视图中的默认功能。

表单视图(FORM)
组件 (widget) 一对一(O2O) 多对一(M2O) 一对多(O2M) 多对多(M2M)
Table / / 前端搜索、分页、排序
submitType="all"
relationUpdateType="all"
前端搜索、分页、排序
submitType="relation"
relationUpdateType="all"
Form submitType="all"
relationUpdateType="all"
submitType="all"
relationUpdateType="all"
/ /
Select submitType="relation"
relationUpdateType="all"
submitType="relation"
relationUpdateType="all"
submitType="relation"
relationUpdateType="all"
submitType="relation"
relationUpdateType="all"
详情视图(DETAIL)
组件 (widget) 一对一(O2O) 多对一(M2O) 一对多(O2M) 多对多(M2M)
Table / / 后端搜索、分页、排序
不支持数据提交
后端搜索、分页、排序
不支持数据提交
Form 仅用于查看
submitType="relation"
relationUpdateType="all"
仅用于查看
submitType="relation"
relationUpdateType="all"
/ /
Select 仅用于查看
submitType="relation"
relationUpdateType="all"
仅用于查看
submitType="relation"
relationUpdateType="all"
仅用于查看
submitType="relation"
relationUpdateType="all"
仅用于查看
submitType="relation"
relationUpdateType="all"

默认表单视图(FORM)

<view type="FORM">
    <element widget="actionBar" slot="actionBar">
        <xslot name="actions" />
    </element>
    <element widget="form" slot="form">
        <xslot name="fields" />
    </element>
</view>

在这个视图中,我们可以获得如下信息:

  • 主视图为表单(FORM)视图。
  • 包含两个主要的element组件widget="actionBar"widget="form"

在我们内置的组件实现中,widget="form"这个组件通过调用后端接口,为整个视图提供数据源。

组件挂载时序图

image.png

``` mermaid
sequenceDiagram

participant FormView as FormView("view type=#quot;FORM#quot;")

participant ActionBar as ActionBarWidget("element widget=#quot;actionBar#quot;")

participant Form as FormWidget("element widget=#quot;form#quot;")

FormView ->> FormView : before mount
FormView ->> ActionBar : before mount
FormView ->> Form : before mount

ActionBar ->> FormView : mounted
Form ->> FormView : mounted
FormView ->> FormView : mounted

FormView ->> FormView : mountedCallChaing hook (VIEW_WIDGET_PRIORITY)

FormView ->> Form : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)

```

组件数据源加载

image.png

``` mermaid
sequenceDiagram

participant FormView as FormView("view type=#quot;FORM#quot;")

participant Form as FormWidget("element widget=#quot;form#quot;")

FormView ->> Form : mountedCallChaing hook (FETCH_DATA_WIDGET_PRIORITY)
Form ->> Form : mounted process (call api)
Form ->> FormView : reloadDataSource
Form ->> FormView : reloadActiveRecords

```

用户行为触发的数据交互

这里列举了一些对象(Object)常用的数据交互时序图,用于帮助大家对数据交互的整体逻辑可以有更清晰的理解。

点击提交动作(ServerAction)(创建/编辑)

image.png

``` mermaid
sequenceDiagram

participant FormView as FormView("view type=#quot;FORM#quot;")

participant Form as FormWidget("element widget=#quot;form#quot;")

participant ServerAction as ServerAction("action actionType=#quot;ServerAction#quot;")

ServerAction ->> FormView : submitCallChaining (call)
FormView ->> Form : submitCallChaining (call)
FormView ->> ServerAction : submitCallChaining (return)

ServerAction ->> ServerAction : execute action (call api)

```

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

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

(0)
oinone的头像oinone
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

  • 自定义组件之手动渲染基础(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4.3.0) 了解组件相关内容。 Class Component(ts)(v4) 自定义组件之自动渲染(组件插槽的使用)(v4) 为什么需要手动渲染 在自定义组件之自动渲染(组件插槽的使用)(v4)文章中,我们介绍了带有具名插槽的组件可以使用DSL模板进行自动化渲染,并且可以用相对简单的方式与元数据进行结合。 虽然自动化渲染在实现基本业务逻辑的情况下,有着良好的表现,但自动化渲染方式也有着不可避免的局限性。 比如:当需要多个视图在同一个位置进行切换。 在我们的平台中,界面设计器的设计页面,在任何一个组件在选中后,需要渲染对应的右侧属性面板。每个面板的视图信息是保存在对应的元件中的。根据元件的不同,找到对应的视图进行渲染。在单个视图中使用自动化渲染是无法处理这一问题的,我们需要一种可以局部渲染指定视图的方式,来解决这一问题。 获取一个视图 使用ViewCache获取视图 export class ViewCache { /** * 通过模型编码和名称获取视图 * @param model 模型编码 * @param name 名称 * @param force 强制查询 * @return 运行时视图 */ public static async get(model: string, name: string, force = false): Promise<RuntimeView | undefined> /** * 通过模型编码、自定义名称和模板获取编译后的视图(此视图非完整视图,仅用于自定义渲染使用) * @param model 模型编码 * @param name 名称(用作缓存key) * @param template 视图模板 * @param force 强制查询 * @return 运行时视图 */ public static async compile( model: string, name: string, template: string, force = false ): Promise<RuntimeView | undefined> } ViewCache#get:用于服务端定义视图,客户端直接获取完整视图信息。 ViewCache#compile:用于客户端定义视图,通过服务端编译填充元数据相关信息,但不包含视图其他信息。 自定义一个带有具名插槽的组件,并提供切换视图的相关按钮 以下是一个自定义组件的完整示例,其使用ViewCache#compile方法获取视图。 view.ts const template1 = `<view> <field data="id" invisible="true" /> <field data="code" label="编码" /> <field data="name" label="名称" /> </view>`; const template2 = `<view> <field data="id" invisible="true" /> <field data="name" label="名称" /> <field data="code" label="编码" /> </view>`; export const templates = { template1, template2 }; ManualDemoWidget.ts import { BaseElementWidget, createRuntimeContextForWidget, FormWidget, RuntimeView, SPI, ViewCache, ViewType, Widget } from '@kunlun/dependencies'; import ManualDemo from './ManualDemo.vue'; import { templates } from './view'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' })) export class ManualDemoWidget extends BaseElementWidget { private formWidget: FormWidget | undefined; public…

    2023年11月1日
    1.1K00
  • 创建与编辑一体化

    在业务操作中,用户通常期望能够在创建页面后立即进行编辑,以减少频繁切换页面的步骤。我们可以充分利用Oinone平台提供的创建与编辑一体化功能,使操作更加高效便捷。 通过拖拽实现表单页面设计 在界面设计器中,我们首先需要设计出对应的页面。完成页面设计后,将需要的动作拖入设计好的页面。这个动作的关键在于支持一个功能,即根据前端传入的数据是否包含id来判断是创建操作还是编辑操作。 动作的属性配置如下: 前端自定义动作 一旦页面配置完成,前端需要对这个动作进行自定义。以下是一个示例的代码: @SPI.ClassFactory( ActionWidget.Token({ actionType: [ActionType.Server], model: '模型', name: '动作的名称' }) ) export class CreateOrUpdateServerActionWidget extends ServerActionWidget { @Widget.Reactive() protected get updateData(): boolean { return true; } } 通过以上步骤,我们实现了一个更智能的操作流程,使用户能够在创建页面的同时进行即时的编辑,从而提高了整体操作效率。这种创建与编辑一体化的功能不仅使操作更加顺畅,同时也为用户提供了更灵活的工作流程。

    2023年11月21日
    1.8K00
  • oio-drawer抽屉

    屏幕边缘滑出的浮层面板。 何时使用 抽屉从父窗体边缘滑入,覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务,操作完成后,可以平滑地回到原任务。 当需要一个附加的面板来控制父窗体内容,这个面板在需要时呼出。比如,控制界面展示样式,往界面中添加内容。 当需要在当前任务流中插入临时任务,创建或预览附加内容。比如展示协议条款,创建子对象。 API 参数 说明 类型 默认值 版本 class 对话框外层容器的类名 string – closable 是否显示左上角的关闭按钮 boolean true closeIcon 自定义关闭图标 VNode | slot destroyOnClose 关闭时销毁 Drawer 里的子元素 boolean false footer 抽屉的页脚 VNode | slot – getTriggerContainer 指定 Drawer 挂载的 HTML 节点 HTMLElement | () => HTMLElement | Selectors ‘body’ height 高度, 在 placement 为 top 或 bottom 时使用 string | number keyboard 是否支持键盘 esc 关闭 boolean true mask 是否展示遮罩 Boolean true maskClosable 点击蒙层是否允许关闭 boolean true placement 抽屉的方向 ‘top’ | ‘right’ | ‘bottom’ | ‘left’ ‘right’ style 可用于设置 Drawer 最外层容器的样式,和 drawerStyle 的区别是作用节点包括 mask CSSProperties – title 标题 string | slot – visible(v-model:visible) Drawer 是否可见 boolean – width 宽度 string | number 378 zIndex 设置 Drawer 的 z-index Number 1000 cancelCallback 点击遮罩层或右上角叉或取消按钮的回调, return true则关闭弹窗 function(e) enterCallback 点击确定回调 function(e)

    2023年12月18日
    1.1K00
  • 如何自定义 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日
    1.8K00
  • 组件SPI机制(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 组件SPI简介 不论是母版、布局还是DSL,所有定义在模板中的标签都是通过组件SPI机制获取到对应Class Component(ts)并继续执行渲染逻辑。 基本概念: 标签:xml中的标签,json中的dslNodeType属性。 Token组件:用于收集一组Class Component(ts)的基础组件。通常该基础组件包含了对应的一组基础能力(属性、函数等) 维度(dsl属性):用于从Token组件收集的所有Class Component(ts)组件中查找最佳匹配的参数。 组件SPI机制将通过指定维度按照有权重的最长路径匹配算法获取最佳匹配的组件。 组件注册到指定Token组件 以BaseFieldWidget这个SPIToken组件为例,可以用如下方式,注册一个可以被field标签处理的自定义组件: (以下示例仅仅为了体现SPI注册的维度,而并非实际业务中使用的组件代码) 注册一个String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String }) ) export class FormStringFieldWidget extends BaseFieldWidget { …… } 注册一个多值的String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 是否多值:是 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且该字段为多值字段 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, multi: true }) ) export class FormStringMultiFieldWidget extends BaseFieldWidget { …… } 注册一个String类型的Hyperlinks组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 组件名称:Hyperlinks 说明: 该字段组件仅可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且组件名称必须指定为Hyperlinks @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, widget: 'Hyperlinks' }) ) export class FormStringHyperlinksFieldWidget extends BaseFieldWidget { …… } 当上述组件全部按顺序注册在BaseFieldWidget这个SPIToken组件中时,将形成一个以BaseFieldWidget为根节点的树: “` mermaidgraph TDBaseFieldWidget —> FormStringFieldWidgetBaseFieldWidget —> FormStringMultiFieldWidgetFormStringFieldWidget —> FormStringHyperlinksFieldWidget“` 树的构建 上述形成的组件树实际并非真实的存储结构,真实的存储结构是通过维度进行存储的,如下图所示: (圆角矩形表示维度上的属性和值,矩形表示对应的组件) “` mermaidgraph TDviewType([viewType: ViewType.Form]) —>ttype([ttype: ModelFieldType.Strng]) —>multi([multi: true]) & widget([widget: &#039;Hyperlinks&#039;]) direction LRttype —> FormStringFieldWidgetmulti —> FormStringMultiFieldWidgetwidget —> FormStringHyperlinksFieldWidget“` 有权重的最长路径匹配 同样以上述BaseFieldWidget组件为例,该组件可用的维度有: viewType:ViewType[Enum] ttype:ModelFieldType[Enum] multi:[Boolean] widget:[String] model:[String] viewName:[String] name:[String] 当field标签被渲染时,我们会组装一个描述当前获取维度的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget": "", // 在dsl中定义的任意值 "model": "", // 在dsl编译后自动填充 "viewName": "", // 当前视图名称 "name": "" // 字段的name属性,在dsl编译后自动填充 } 当我们需要使用FormStringHyperlinksFieldWidget这个组件时,我们在dsl中会使用如下方式定义: <view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel"> <field data="name" widget="Hyperlinks" /> </view> 此时,我们虽然没有在dsl中定义维度中的其他信息,但在dsl返回到前端时,经过了后端编译填充了对应元数据相关属性,我们可以得到如下所示的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget":…

    2023年11月1日
    1.1K00

Leave a Reply

登录后才能评论