自定义视图部分区域渲染设计器的配置

自定义视图与界面设计器配置对接

在日常开发中,我们经常会遇到自定义视图的需求。自定义视图不仅需要与平台机制结合,还要实现与界面设计器中配置的字段和动作的无缝对接。本文将介绍如何将自定义视图与界面设计器中配置的字段和动作的无缝对接,实现字段和动作的渲染。

用大白话来讲就是:当前页面一部分是自定义的,一部分是设计器生成的

代码地址

目录

  1. 自定义表单视图与字段、动作的结合
  2. 自定义表格视图与字段、动作的结合

自定义表单视图与字段、动作的结合

首先需要在界面设计器配置好对应界面,虽然配置的页面样式跟期望展示的 UI 的不一样,但是数据的分发、汇总以及动作的交互也是一致的,所以我们可以通过自定义的方式替换这个页面的 UI,但是数据以及动作,是完全可以通过平台的能力获取的。

注册表单对应的 SPI

// CustomFormWidget.ts

import CustomForm from './CustomForm.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Form,
    widget: 'CustomFormWidget'
  })
)
export class CustomFormWidget extends FormWidget {
  public initialize(props: BaseElementObjectViewWidgetProps): this {
    super.initialize(props);
    this.setComponent(CustomForm);
    return this;
  }
}
<!-- CustomForm.vue -->

<template>
  <div class="custom-form-container">
    <div class="custom-form-tip">自定义视图</div>
  </div>
</template>
<script lang="ts">
  import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies';
  import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      formData: {
        type: Object as PropType<Record<string, any>>,
        default: () => ({})
      }
    }
  });
</script>

在上述的代码中,我们继承的是 FormWidget,那么这个页面会自动发起对应的请求,所有的数据都在 formData 中。

表单视图渲染动作

在最开始我们讲到过,当前页面是在界面设计器配置过,所有在CustomFormWidget里面是可以拿到当前页面配置的元数据信息,所以我们可以拿到界面设计器配置的字段跟动作

/// CustomFormWidget.ts

@Widget.Method()
protected renderActionVNodes() {
  // 从dsl中获取actionBar,actionBar里面包含了界面设计器配置的动作
  const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actionBar');

  if (actionBar) {
    // actionBar.widgets 就是界面设计器配置的动作,借助平台提供的DslRender.render方法,将对应的dsl转换成VNode
    return actionBar.widgets.map((w, index) =>
      DslRender.render({
        ...w,
        __index: index
      })
    );
  }

  return null;
}

因为 renderActionVNodes 方法返回的是 VNode,所以我们必须借助 vue 的 render 函数才能渲染。

<!-- ActionRender.vue -->

<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderActionVNodes: {
        type: Function,
        required: true
      }
    },
    render() {
      return this.renderActionVNodes();
    }
  });
</script>

ActionRender.vue中,通过 props 接收renderActionVNodes 方法,最终在 render 函数中执行,所以只需要在CustomForm.vue导入ActionRender.vue

<template>
  <div class="custom-form-container">
    <div class="custom-form-tip">自定义视图</div>

    <p style="color: red">下面是通过平台机制生成的动作</p>

    <div style="display: flex; gap: 10px">
      <action-render :renderActionVNodes="renderActionVNodes"></action-render>
    </div>
  </div>
</template>
<script lang="ts">
  import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies';
  import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue';
  import ActionRender from './components/ActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderActionVNodes: {
        type: Function,
        required: true
      }
    },
    components: {
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-form-container {
    .custom-form-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

表单视图渲染字段

渲染界面设计器配置的字段和刚刚讲到的动作是一致的。

// CustomFormWidget.ts
@Widget.Method()
  protected renderFieldVNodes() {
    // 获取界面设计器配置的字段
    const modelFields = this.metadataRuntimeContext.model.modelFields;

    if (modelFields.length) {
      //借助平台提供的DslRender.render方法,将对应的dsl转换成VNode
      return modelFields.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

因为renderFieldVNodes 返回的也是 VNode,所以我们还是需要借助 vue 的 render 函数来渲染.

<!-- FieldRender.vue -->
<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderFieldVNodes: {
        type: Function,
        required: true
      }
    },
    render() {
      return this.renderFieldVNodes();
    }
  });
</script>

最终也是在CustomForm.vue中导入FieldRender.vue

下面是完整的代码:

// CustomFormWidget.ts

import {
  BaseElementObjectViewWidgetProps,
  BaseElementWidget,
  DslRender,
  FORM_WIDGET,
  FormWidget,
  SPI,
  ViewType,
  Widget
} from '@kunlun/dependencies';

import CustomForm from './CustomForm.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Form,
    widget: 'CustomFormWidget'
  })
)
export class CustomFormWidget extends FormWidget {
  public initialize(props: BaseElementObjectViewWidgetProps): this {
    super.initialize(props);
    this.setComponent(CustomForm);
    return this;
  }

  /**
   * 渲染字段VNode
   */
  @Widget.Method()
  protected renderFieldVNodes() {
    const modelFields = this.metadataRuntimeContext.model.modelFields;

    if (modelFields.length) {
      return modelFields.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

  /**
   * 渲染动作VNode
   */
  @Widget.Method()
  protected renderActionVNodes() {
    const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actionBar');

    if (actionBar) {
      return actionBar.widgets.map((w, index) =>
        DslRender.render({
          ...w,
          __index: index
        })
      );
    }

    return null;
  }

  @Widget.Reactive()
  protected showFieldVNode = false;

  protected async mountedProcess() {
    await super.mountedProcess();

    this.showFieldVNode = true;
  }
}
<!-- CustomForm.vue -->
<template>
  <div class="custom-form-container">
    <div class="custom-form-tip">自定义视图</div>

    <p style="color: red">下面是通过平台机制生成的字段</p>

    <field-render v-if="showFieldVNode" :renderFieldVNodes="renderFieldVNodes"></field-render>

    <p style="color: red">下面是通过平台机制生成的动作</p>

    <div style="display: flex; gap: 10px"><action-render :renderActionVNodes="renderActionVNodes"></action-render></div>
  </div>
</template>
<script lang="ts">
  import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies';
  import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue';
  import FieldRender from './components/FieldRender.vue';
  import ActionRender from './components/ActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderActionVNodes: {
        type: Function,
        required: true
      },
      renderFieldVNodes: {
        type: Function,
        required: true
      },
    showFieldVNode: {
      type: Boolean,
      default: false
    }
    },
    components: {
      FieldRender,
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-form-container {
    .custom-form-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

注册 layout

registerLayout(
  `<view type="FORM">
    <element widget="CustomFormWidget" slot="form">
        <xslot name="fields" slotSupport="pack,field" />
    </element>
  </view>`,
  {
    moduleName: 'moduleName',
    model: 'model',
    viewType: ViewType.Form,
    actionName: 'actionName'
  }
);

自定义表格视图与字段动作的结合

注册表格对应的 SPI

// CustomTableWidget.ts

import {
  BaseElementObjectViewWidgetProps,
  BaseElementWidget,
  DslRender,
  SPI,
  TableWidget,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import CustomTable from './CustomTable.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Table,
    widget: 'CustomTableWidget'
  })
)
export class CustomTableWidget extends TableWidget {
  public initialize(props: BaseElementObjectViewWidgetProps): this {
    super.initialize(props);
    this.setComponent(CustomTable);
    return this;
  }

  /**
   * 渲染行内动作VNode
   */
  @Widget.Method()
  protected renderRowActionVNodes() {
   // const table = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'table');

    // const rowAction = table?.widgets.find((w) => w.slot === 'rowActions');
    // this.refreshCallChaining;
    // if (rowAction) {
    //   return rowAction.widgets.map((w) => DslRender.render(w));
    // }

    // return null;

    const modelActions = this.metadataRuntimeContext.model.modelActions;

    if (modelActions.length) {
      return modelActions.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

  /**
   * 渲染动作VNode
   */
  @Widget.Method()
  protected renderActionVNodes() {
    const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actions');

    if (actionBar) {
      return actionBar.widgets.map((w, index) =>
        DslRender.render({
          ...w,
          __index: index
        })
      );
    }

    return null;
  }
}

在上述代码中,我们继承的是TableWidget,所以页面也会自动发起查询,并且定义了renderRowActionVNodesrenderActionVNodes方法,用于渲染行内动作和动作。

表格视图渲染动作

<!-- CustomTable.vue -->
<template>
  <div class="custom-table-container">
    <div class="custom-table-tip">自定义视图</div>

    <div style="display: flex; gap: 10px">
      <p style="color: red; margin-top: 10px">这是表格的动作</p>
      <action-render :renderActionVNodes="renderActionVNodes"></action-render>
    </div>

    <p style="color: red; margin-top: 10px">这是表格的数据跟行内动作</p>
    <div>
      <div v-for="(row, index) in dataSource" :key="row.index">
        <div style="display: flex">
          <div><span>行数据</span> <span> ID:{{ row.id }}</span></div>
          <div>
            <span>行动作</span>
            <span>
              <row-action-render
                :renderRowActionVNodes="renderRowActionVNodes"
                :row="row"
                :rowIndex="index"
                :parentHandle="currentHandle"
              ></row-action-render>
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
  import { defineComponent, PropType } from 'vue';
  import ActionRender from './components/ActionRender.vue';
  import RowActionRender from './components/RowActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
        currentHandle: {
          type: String,
          required: true
        },
      renderActionVNodes: {
        type: Function,
        required: true
      },
      renderRowActionVNodes: {
        type: Function,
        required: true
      },
      dataSource: {
        type: Array as PropType<any[]>,
        default: () => []
      }
    },
    components: {
      RowActionRender,
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-table-container {
    .custom-table-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

ActionRender.vue跟表单中的ActionRender.vue一样,内部也是在 render 函数中调用renderActionVNodes

RowActionRender.vue有点特殊,下面是对应的代码(固定写法)。

<script lang="ts">
  import { ActionBar, RowActionBarWidget, uniqueKeyGenerator } from '@kunlun/dependencies';
  import { debounce } from 'lodash-es';
  import { createVNode, defineComponent } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      row: {
        type: Object,
        required: true
      },
      rowIndex: {
        type: Number,
        required: true
      },
      renderRowActionVNodes: {
        type: Function,
        required: true
      },
      parentHandle: {
          type: String,
          required: true
        }
    },
    render() {
      const vnode = this.renderRowActionVNodes();

      return createVNode(
        ActionBar,
        {
          widget: 'rowAction',
          parentHandle: this.parentHandle,
          inline: true,
          activeRecords: this.row,
          rowIndex: this.rowIndex,
          key: this.rowIndex,
          refreshWidgetRecord: debounce((widget?: RowActionBarWidget) => {
            if (widget) {
              widget.setCurrentActiveRecords(this.row);
            }
          })
        },
        {
          default: () => vnode
        }
      );
    }
  });
</script>

注册 layout

import { registerLayout, ViewType } from '@kunlun/dependencies';

registerLayout(
  `<view type="TABLE">
    <element widget="CustomTableWidget" slot="table" slotSupport="field">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" slotSupport="field" />
            <element widget="rowActions" slot="rowActions" slotSupport="action" />
        </element>
</view>`,
  {
    moduleName: 'resource',
    model: 'resource.ResourceProvince',
    viewType: ViewType.Table
  }
);

本文讲解了自定义视图与界面设计器中配置的字段和动作的无缝对接,以实现自定义视图的功能,本质上是将界面设计器配置的字段、动作,渲染成对应的 VNode,然后在自定义的页面渲染。

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

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

Like (1)
汤乾华's avatar汤乾华数式员工
Previous 2024年9月12日 am11:23
Next 2024年9月12日 pm10:16

相关推荐

  • 表格如何支持表尾合计计算

    介绍 可以通过扩展TableWidget.ts实现 示例代码 import { BaseElementWidget, DslDefinitionType, SPI, TableWidget, ViewType, Widget } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ type: ViewType.Table, widget: 'table', model: 'resource.k2.Model0000000109', viewName: '移动端品牌_TABLE_0000000000021513' }) ) export class FooterStatisticsTable extends TableWidget { public initialize(props) { if (props.template) { props.template?.widgets?.forEach((a) => { if (a.dslNodeType === DslDefinitionType.FIELD && this.statisticsFieldList.includes(a.name)) { a.statistics = true; } }); } super.initialize(props); return this; } // 需要表尾做合并的字段名称 public statisticsFieldList = ['fansNum']; @Widget.Reactive() protected get showFooter(): boolean | undefined { return true; } } 效果预览

    2024年10月14日
    2.1K00
  • 【前端】环境配置(v4)

    环境配置 前端环境配置包含.env编译时配置和RuntimeConfig运行时配置 编译时配置 .env 属性 类型 默认值 作用 BASE_PATH [String] 统一配置URL请求路径前缀 STATIC_IMG [String] 静态资源路径 RUNTIME_CONFIG_BASE_URL [String] 运行时配置文件URL请求路径前缀 RUNTIME_CONFIG_FILENAME [String] manifest 运行时配置文件名 MESSAGE_LEVEL DEBUG、SUCCESS、INFO、WARN、ERROR 消息级别 BASE_PATH 当配置BASE_PATH=/test时,前端所有请求就将添加该前缀。 如/login登录页,将自动修改为/test/login MESSAGE_LEVEL 当配置消息级别时,全局的消息通知将根据消息级别进行过滤。 DEBUG:允许全部级别的消息通知。 SUCCESS:仅允许ERROR、WARN、INFO、SUCCESS级别的消息通知。 INFO:仅允许ERROR、WARN、INFO级别的消息通知。 WARN:仅允许ERROR、WARN级别的消息通知。 ERROR:仅允许ERROR级别的消息通知。 运行时配置 RuntimeConfig 运行时配置是作为编译时配置的补充。 manifest.js runtimeConfigResolve({ …… }); 开发时使用运行时配置 创建{PROJECT_NAME}/public/manifest.js文件。 如kunlun-boot/public/manifest.js 生产环境使用运行时配置 在nginx中配置的dist访问路径下,创建manifest.js文件。 若将/opt/pamirs/frontend/dist作为访问路径,则创建/opt/pamirs/frontend/dist/manifest.js文件。 注意事项 编译时可能使用编译时配置改变运行时配置文件的路径和文件名,注意文件名和访问路径必须匹配。 在浏览器中访问时,nginx或者浏览器会对js文件会进行缓存处理,会导致运行时配置不能及时生效。最好的办法是配置nginx时,禁用manifest.js文件缓存。 在manifest.js文件中建议不要包含任何与配置项无关的字符(包括注释),该文件在通过网络传输过程中,无法处理这些无效内容。 面包屑配置 breadcrumb 配置方式 runtimeConfigResolve({ breadcrumb?: boolean | BreadcrumbConfig }); BreadcrumbConfig /** * 面包屑配置 */ export interface BreadcrumbConfig extends RuntimeConfigOptions, EnabledConfig { /** * <h3>是否启用</h3> * <p>启用时,需配合mask渲染对应的面包屑组件</p> * <p>默认: 开启</p> */ enabled?: boolean; /** * <h3>首页配置</h3> * <p>boolean值与{@link BreadcrumbHomepageConfig#enabled}等效</p> */ homepage?: boolean | BreadcrumbHomepageConfig; } /** * 首页配置 */ export interface BreadcrumbHomepageConfig extends RuntimeConfigOptions, EnabledConfig { /** * <h3>首项显示首页</h3> * <p>默认: 开启</p> */ enabled?: boolean; /** * <h3>首页类型</h3> * <p>默认: 'application'</p> */ type?: 'application' | 'module'; } 多选项卡配置 multiTabs 配置方式 runtimeConfigResolve({ multiTabs?: boolean | MultiTabsConfig }); MultiTabsConfig /** * 多标签页配置 */ export interface MultiTabsConfig extends RuntimeConfigOptions, EnabledConfig { /** * <h3>是否启用(非运行时)</h3> * <p>启用时,需配合mask渲染对应的多标签页管理组件</p> * <p>默认: 开启</p> */ enabled?: boolean; /** * <h3>是否使用内联多标签页</h3> * <p>仅在使用默认mask时生效</p> */ inline?: boolean; /** * <h3>显示模块Logo</h3> * <p>默认: 开启</p> */ showModuleLogo?: boolean; /** * <h3>最多标签页数量</h3> *…

    前端 2023年11月1日
    2.3K00
  • OioNotification 通知提醒框

    全局展示通知提醒信息。 何时使用 在系统四个角显示通知提醒信息。经常用于以下情况: 较为复杂的通知内容。 带有交互的通知,给出用户下一步的行动点。 系统主动推送。 API OioNotification.success(title,message, config) OioNotification.error(title,message, config) OioNotification.info(title,message, config) OioNotification.warning(title,message, config) config 参数如下: 参数 说明 类型 默认值 版本 duration 默认 3 秒后自动关闭 number 3 class 自定义 CSS class string –

    2023年12月18日
    1.1K00
  • 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:平台内置了多种Query和Mutation定义,通过模型继承关系将自动生成,无需手动定义。比如Query定义包括queryOne、queryPage等;Mutation定义包括create、update等。特殊情况下,默认逻辑无法满足时,服务端通常采用函数重载的方式进行替换,客户端则无需关心。 生成规则 type DemoModel:通过模型编码demo.DemoModel取.分隔后的最后一位,并转换为大驼峰格式。字段与声明类型一致。 type DemoModelInput:动作入参定义,未做特殊声明的情况下与模型定义一致。 type DemoModelQuery和type DemoModelMutation:Query和Mutation为固定后缀,分别生成动作相关类型。当函数类型为QUERY时,使用Query后缀,其他情况使用Mutation后缀。 (此处仅做简单解释,详细生成规则过于复杂,客户端无需关心) Query类型的GQL示例 query { demoModelQuery { queryOne(query: { id: ${id} }) { id name isEnabled } } } Mutation类型的GQL示例 mutation…

    2023年11月1日
    2.6K00
  • 组件数据交互基础(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日
    2.0K00

Leave a Reply

Please Login to Comment