TS 结合 Vue 实现动态注册和响应式管理

基础知识

1: 面向对象

面向对象编程是 JavaScript 中一种重要的编程范式,它帮助开发者通过类和对象组织代码,提高代码的复用性。

2: 装饰器

装饰器在 JavaScript 中是用于包装 class 或方法的高阶函数

为了统一术语,下面的内容会把装饰器讲成注解

在 oinone 平台中,无论是字段还是动作,都是通过 ts + vue 来实现的,ts 中是面向对象的写法,所有的属性、方法建议都在对应的 class 中,如果有通用的属性跟方法,可以放在一个公共的 class 中,然后通过继承来实现,这样便于维护。

<!-- FormString.vue -->
<template>
  <div>
    <p>用户名: {{userName}}</p>
    <button @click="updateUser({name: '王五'})">修改用户名</button>
  </div>
</template>
<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    props: {
      userName: {
        type: String,
        default: ''
      },
      userInfo: {
        type: Object,
        default: () => ({})
      },
      updateUser: {
        type: Function,
        default: () => () => ({})
      }
    }
  });
</script>
import FormString from './FormString.vue'

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.String
  })
)
export class FormCustomStringFieldWidget extends FormFieldWidget {
  public initialize(props) {
    super.initialize(props); // 调用父类方法,确保继承的属性和方法正常初始化
    this.setComponent(FormString); // 注册 Vue 组件,这样该 Widget 就会渲染 FormString 组件
    return this;
  }

  public otherInfo = {
    name:'张三'
  }

  @Widget.Reactive()
  public userInfo = {
    name:'李四'
  }

  @Widget.Reactive()
  public get userName() {
    return this.userInfo.name
  }

  @Widget.Method()
  public updateUser userName(user) {
     this.userInfo = user
  }

  public updateOtherUser userName(user) {
     this.otherUser = user
  }
}

这段代码定义了一个 FormCustomStringFieldWidget 类,用于处理表单中 String 类型字段的展示和交互。该类继承自 FormFieldWidget,并使用了多种注解和特性来实现不同功能。下面是对代码的详细讲解。

SPI 讲解

@SPI.ClassFactory()

无论是自定义字段还是动作,或者是自定义 mask、layout,都会用到@SPI.ClassFactory来注册,@SPI.ClassFactory 是一个注解,它标记了该类是通过工厂模式注册的。

在前端中,所有的注解(装饰器)本质上还是高阶函数,下面是一段伪代码。

const SPI = {
  ClassFactory(token) {
    return (targetClass) => {
      // todo something
    };
  }
};

class MyClass {}

SPI.ClassFactory(注册条件)(MyClass);

所以我们在使用@SPI.ClassFactory的时候,就会根据注册条件把对应的 class 注册到对应的工厂中。

注册条件

FormFieldWidget.Token({
  viewType: [ViewType.Form, ViewType.Search],
  ttype: ModelFieldType.String
});

这段代码调用FormFieldWidget.Token函数,生成一个 token,这个 token 会作为参数传递给@SPI.ClassFactory,然后@SPI.ClassFactory会根据这个 token 来注册对应的 class。

不同类型的 widget 调用的 token 函数不同:

注册条件集合

字段注册条件

  • 表单(详情、搜索、画廊)字段: FormFieldWidget.Token
  • 表格字段: BaseFieldWidget.Token

下面是参数描述和含义:

属性 类型 说明
viewType ViewType | ViewType[] 当前视图类型,支持单一或多个视图类型。
widget string | string[] 组件名称,可以是单个字符串或多个组件名称。
ttype ModelFieldType | ModelFieldType[] 字段业务类型,支持单一或多个业务类型。
multi boolean 是否多值,true 表示字段支持多个值。
model string | string[] 指定模型名称,可以是单一或多个模型。
viewName string | string[] 指定视图名称,可以是单一或多个视图名称。
name string 指定字段的 name 属性,用于业务逻辑识别。

当我们注册 字段 SPI 的时候,对应注册条件基本只会用到viewTypettypewidget、这三个属性,其他属性都是可选的。

动作注册条件

  • 动作: ActionWidget.Token

下面是参数描述和含义:

属性 类型 说明
viewType ViewType | ViewType[] 当前视图类型,支持单一或多个视图类型。
actionType ActionType | AiewType[] 当前动作类型,支持单一或多个动作类型。
widget string | string[] 组件名称,可以是单个字符串或多个组件名称。
target ViewActionTarget | ViewActionTarget[] 打开方式 (视图动作专属)
viewName string | string[] 指定视图名称,可以是单一或多个视图名称。
model string | string[] 指定模型名称,可以是单一或多个模型。
name string 指定动作的 name 属性,用于业务逻辑识别。

当我们注册 动作 SPI 的时候,对应注册条件基本只会用到actionTypemodelname、这三个属性,其他属性都是可选的。

视图、layout 注册条件

  • 动作: BaseElementWidget.Token

下面是参数描述和含义:

属性 类型 说明
viewType ViewType | ViewType[] 当前视图类型,支持单一或多个视图类型。
inline boolean 当前是否为内嵌视图。
widget string | string[] 组件名称,可以是单个字符串或多个组件名称。
viewName string | string[] 指定视图名称,可以是单一或多个视图名称。
model string | string[] 指定模型名称,可以是单一或多个模型。

当我们注册 layout 或者视图 SPI 的时候,对应注册条件基本只会用到viewTypewidget、这两个个属性,其他属性都是可选的。

mask 注册条件

  • 动作: MaskWidget.Token

下面是参数描述和含义:

属性 类型 说明
widget string | string[] 组件名称,可以是单个字符串或多个组件名称。

当我们注册 mask SPI 的时候,对应注册条件只会用到widget

路由注册条件

  • 动作: RouterWidget.Token

下面是参数描述和含义:

属性 类型 说明
widget string | string[] 组件名称,可以是单个字符串或多个组件名称。

当我们注册 路由 SPI 的时候,对应注册条件只会用到widget

SPI 覆盖

在开发中,有时我们需要对平台底层已注册的 SPI 进行覆盖。这种情况通常出现在需要修改或扩展某些功能时。下面将介绍如何安全地覆盖 SPI。

以字段为例,加入我们需要覆盖平台默认的字段,这个字段是 Form 表单里面的String类型,我们希望这个字段可以支持业务扩展的能力,那么我们可以通过如下方式来扩展这个字段:

在 5.0.x 版本中,平台部分源码是开放的,所以我们可以看到平台默认的字段源码,FormStringFieldSingleWidget就是 Form 表单里面的String类型对应的 class,它的 SPI 是:

@SPI.ClassFactory(
  BaseFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.String
  })
)

如果想覆盖它,只需要 SPI 注册条件一致就行。

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.String
  })
)
class ExistingStringFieldWidget extends FormStringFieldSingleWidget {
  // 平台已有实现
}

注册 vue 组件

FormCustomStringFieldWidget中,我们定义了initialize函数里面写了一点代码。

import FormString from './FormString.vue'

class FormCustomStringFieldWidget{
  public initialize(props) {
    super.initialize(props);
    this.setComponent(FormString);
    return this;
  }
}

initialize函数是固定写法,super.initialize(props)是用来调用父类的方法,this.setComponent(FormString)是将 vue 组件注册到当前 widget 中,当自定义的字段不需要重写 vue 组件的时候,那么就不需要重写initialize函数,只需要继承对应的 class 重写对应的属性、方法即可

当自定义的字段、动作、视图、mask 需要使用自己的 vue 组件时,才需要重写initialize函数

ts 与 vue 之间的联动

Widget.Reactive()

FormCustomStringFieldWidget中,我们定义了userInfouserName两个属性,通过Widget.Reactive()注解,将这两个属性变成响应式属性,这样在 vue 组件中中,就可以通过 props 来接受两个属性,otherInfo属性没有打上任何注解,说明它是一个普通的属性,在对应的 vue 文件里面是获取不到的。

当定义 userName 时,我们为其加上了 get 属性,这会将它转换为一个计算属性,方便在 Vue 组件中动态更新。

所有可以理解成:

 @Widget.Reactive()
 public userInfo = {}
 等价于 ↓
 const  userInfo = ref({})

  @Widget.Reactive()
  public get userName() {
    return this.userInfo.name
  }
   等价于  ↓
  const  userName = computed(() => userInfo.name)

Widget.Method()

在上述代码中,我们还定义了updateUserupdateOtherUser方法,通过updateUser上使用了Widget.Method()注解,将这个方法变成一个响应式方法,这样在 vue 组件中,就可以通过 props 来接受这个方法,调用这个方法,实现数据的更新,updateOtherUser是一个普通的方法,在 vue 文件中是获取不到的。

最终我们可以得出一个结论,在 ts 中,如果定义的属性跟方法需要在 vue 中获取,那么就要加上Widget.Reactive()注解,如果是方法,就加上Widget.Method()注解,如果该属性是一个计算属性,就加上get属性。

伪代码实现:

import { ref, computed } from 'vue';

const Widget = {
  Reactive(target, key, description) {
    if (description.get) {
      target[key] = computed(description.get);
    } else {
      target[key] = ref(target[key]);
    }
  },

  Method(target, key) {
    target[key] = ref(target[key]);
  }
};

完整案例:用户信息管理

场景:我们将创建一个用户信息管理的小模块,允许用户查看和修改他们的个人信息,包括姓名和邮箱地址。我们将用 Vue 组件展示这些信息,并使用 TS 中的类来管理逻辑。此案例将展示如何使用 @Widget.Reactive() 和 @Widget.Method() 注解来确保数据和方法的响应式更新。

第一步:Vue 组件定义

我们首先定义一个简单的 Vue 组件,用于显示和更新用户信息。

<!-- UserInfo.vue -->
<template>
  <div>
    <p>姓名: {{ userName }}</p>
    <p>邮箱: {{ userEmail }}</p>
    <button @click="updateUserInfo({ name: '李四', email: 'li.si@example.com' })">修改用户信息</button>
  </div>
</template>

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

  export default defineComponent({
    props: {
      userName: {
        type: String,
        default: ''
      },
      userEmail: {
        type: String,
        default: ''
      },
      updateUserInfo: {
        type: Function,
        default: () => () => ({})
      }
    }
  });
</script>

第二步:TypeScript 类定义

我们在 TypeScript 中定义一个类 FormStringUserFieldWidget,用于管理用户信息。我们使用 @Widget.Reactive() 和 @Widget.Method() 来确保数据的响应式更新和方法的可用性。

import UserInfo from './UserInfo.vue';

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: [ViewType.Form],
    ttype: ModelFieldType.String,
    widget: 'FormStringUserFieldWidget'
  })
)
export class FormStringUserFieldWidget extends FormFieldWidget {
  // 注册 Vue 组件
  public initialize(props: any) {
    super.initialize(props);
    this.setComponent(UserInfo); // 将 UserInfo.vue 组件注册到当前 Widget
    return this;
  }

  // 用户基础信息
  @Widget.Reactive()
  public userInfo = {
    name: '张三',
    email: 'zhang.san@example.com'
  };

  // 返回用户姓名的计算属性
  @Widget.Reactive()
  public get userName() {
    return this.userInfo.name;
  }

  // 返回用户邮箱的计算属性
  @Widget.Reactive()
  public get userEmail() {
    return this.userInfo.email;
  }

  // 更新用户信息的方法
  @Widget.Method()
  public updateUserInfo(newInfo: { name: string; email: string }) {
    this.userInfo = { ...this.userInfo, ...newInfo };
  }
}

第三步:案例解析

  • initialize 方法:
    • 我们使用 initialize 方法来注册 Vue 组件 UserInfo.vue。当这个类被实例化时,Vue 组件会被渲染,并且与类中的响应式数据和方法建立连接。
  • userInfo 属性:
    • 这是用户的基本信息对象,包含 name 和 email 两个字段。我们使用 @Widget.Reactive() 注解将 userInfo 变成响应式数据,这样在 Vue 组件中可以随时获取和更新。
  • userName 和 userEmail 计算属性:
    • 我们使用 get 关键字为 userName 和 userEmail 定义了计算属性,并使用 @Widget.Reactive() 注解,这样 Vue 组件可以根据 userInfo 的变化自动更新显示内容。
  • updateUserInfo 方法:
    • 这是用于更新用户信息的响应式方法。通过 @Widget.Method() 注解,我们确保 Vue 组件能够调用此方法。点击 Vue 组件中的按钮后,会触发 updateUserInfo 方法更新用户的姓名和邮箱。
  • Vue 组件
    • 我们通过 props 接收了从 TypeScript 类传递过来的 userName、userEmail 和 updateUserInfo 方法。点击按钮后,updateUserInfo 方法会被调用,用户信息将被更新。

通过这个完整的案例,我们展示了如何将 TypeScript 与 Vue 结合,通过 @Widget.Reactive() 和 @Widget.Method() 实现数据的响应式联动和方法调用。

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

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

(0)
汤乾华的头像汤乾华数式员工
上一篇 2024年9月21日 am10:32
下一篇 2024年9月21日 pm5:51

相关推荐

  • 表格字段API

    BaseTableFieldWidget 表格字段的基类. 示例 class MyTableFieldClass extends BaseTableFieldWidget{ } 内置常见的属性 dataSource 当前表格数据 rootData 根视图数据 activeRecords 当前选中行 userPrefer 用户偏好 width 单元格宽度 minWidth 单元格最小宽度 align 内容对齐方式 headerAlign 头部内容对齐方式 metadataRuntimeContext 当前视图运行时的上下文,可以获取当前模型、字段、动作、视图等所有的数据 urlParameters 获取当前的url field 当前字段 详细信息 用来获取当前字段的元数据 model 当前模型 详细信息 用来获取当前模型的元数据 view 当前视图 详细信息 界面设计器配置的视图dsl disabled 是否禁用 详细信息 来源于界面设计器的配置 invisible 当前字段是否不可见 详细信息 来源于界面设计器的配置,true -> 不可见, false -> 可见 required 是否必填 详细信息 来源于界面设计器的配置,如果当前字段是在详情页,那么是false readonly 是否只读 详细信息 来源于界面设计器的配置,如果当前字段是在详情页、搜索,那么是false label 当前字段的标题 详细信息 用来获取当前字段的标题 内置常见的方法 renderDefaultSlot 渲染单元格内容 示例 @Widget.Method() public renderDefaultSlot(context): VNode[] | string { // 当前单元格的数据 const currentValue = this.compute(context) as string[]; return [createVNode('div', { class: 'table-string-tag' }, currentValue)]; } renderHeaderSlot 自定义渲染头部 示例 @Widget.Method() public renderHeaderSlot(context: RowContext): VNode[] | string { const children = [createVNode('span', { class: 'oio-column-header-title' }, this.label)]; return children; } getTableInstance 获取当前表格实例(vxe-table) getDsl 获取界面设计器的配置

    2023年11月16日
    1.2K00
  • 【前端】默认布局模板(v4)

    默认母版(Mask) PC端默认母版 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask> PC端默认内联Tabs母版(<multi-tabs inline="true" />) <mask> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <block height="100%" flex="1 0 0" flexDirection="column" alignContent="flex-start" flexWrap="nowrap" overflow="hidden"> <multi-tabs inline="true" /> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </block> </container> </mask> 移动端默认母版 <mask> <widget widget="user" /> <widget widget="nav-menu" app-switcher="true" menu="true" /> <widget widget="main-view" height="100%" /> </mask> PC端默认布局(Layout) 表格视图(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> 表单视图(FORM) <view type="FORM"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="form" slot="form"> <xslot name="fields" /> </element> </view>…

    2023年11月1日
    1.4K01
  • 前端自定义字段与视图最佳方案

    自定义视图获取数据 在某些情况下,oinone 提供的默认视图无法满足需求,这时我们就需要自定义视图。通常,虽然视图的 UI 不足以满足要求,但数据结构是不变的。此时,重点是修改页面 UI,数据的请求与回填可以利用平台默认的能力。 如何实现? 界面设计器的使用 你可以通过界面设计器先配置页面,平台在运行时会根据设计器生成对应的 GraphQL 请求,并自动回填数据。 视图的数据结构 视图的数据类型分为可以分为 Object 跟 List Object 代表当前视图的数据结构是对象 List 代表当前视图的数据结构是数组。 如果我们将 Object 跟 List 分的更细一点就变成了这样: 1: Object: 对象,代表当前视图的数据结构是单个对象,例如:表单视图、详情视图1: List: 对象数组,代表当前视图的数据结构数组,例如:表格视图、卡片视图、画廊视图 视图类型 平台组件 数据属性 表格视图 TableWidget dataSource 画廊视图 GalleryWidget dataSource 表单视图 FormWidget formData 详情视图 DetailWidget formData 自定义视图时,需要先确认当前视图的类型,再通过界面设计器进行页面配置。前端部分只需继承相应的组件,平台底层会自动处理接口数据的获取与回填。 表单视图示例: import Form from './Form.vue'; // 自定义表单视图 @SPI.ClassFactory( BaseElementWidget.Token({ widget: 'custom-form' }) ) export class CustomForm extends FormWidget { public initialize(props: Props) { super.initialize(props); this.setComponent(Form); return this; } } Vue 组件: <template></template> <script lang="ts"> export default defineComponent({ props: { formData: { // 当前表单的数据 type: Object, default: () => ({}) } } }); </script> 自定义layout // 原始的layout模版 <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> //自定义的layout模版 <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="custom-form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> 其实就是把 widget="form" 改成 widget="custom-form" 表格视图示例: import Table from './Table.vue'; // 自定义表格视图 @SPI.ClassFactory( BaseElementWidget.Token({ widget: 'custom-table' }) ) export class CustomTable extends TableWidget { public initialize(props: Props) { super.initialize(props); this.setComponent(Table); return this; } } Vue 组件: <template></template> <script…

    2024年10月17日
    1.9K00
  • 列表视图、卡片视图切换

    在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式. 这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格跟卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。 我们用资源模块下面的国家菜单来实现这么一个功能这是对应的URL: http://localhost:8080/page;module=resource;viewType=TABLE;model=resource.ResourceCountry;action=resource%23%E5%9B%BD%E5%AE%B6;scene=resource%23%E5%9B%BD%E5%AE%B6;target=OPEN_WINDOW;menu=%7B%22selectedKeys%22:%5B%22%E5%9B%BD%E5%AE%B6%22%5D,%22openKeys%22:%5B%22%E5%9C%B0%E5%9D%80%E5%BA%93%22,%22%E5%9C%B0%E5%8C%BA%22%5D%7D 源码下载 views 创建外层的视图容器 刚刚我们讲过,不管是表格还是卡片,它都在当前的视图里面,所以我们需要写一个视图容器来包裹它们,并且对应的容器里面允许拆入表格跟卡片,我们先创建TableWithCardViewWidget.ts // TableWithCardViewWidget.ts import { BaseElementWidget, SPI, Widget } from '@kunlun/dependencies'; import TableWithCardView from './TableWithCardView.vue'; enum ListViewType { TABLE = 'table', CARD = 'card' } @SPI.ClassFactory( BaseElementWidget.Token({ widget: 'TableWithCardViewWidget' }) ) export class TableWithCardViewWidget extends BaseElementWidget { @Widget.Reactive() private listViewType: ListViewType = ListViewType.TABLE; // 当前视图展示的类型,是展示卡片还是表格 public initialize(props) { if (!props.slotNames) { props.slotNames = ['tableWidget', 'cardWidget']; } super.initialize(props); this.setComponent(TableWithCardView); return this; } } 在TableWithCardViewWidget中的initialize函数中,我们定义了两个插槽: tableWidget、cardWidget,所以需要在对应的vue文件里面里接收这两个插槽 <template> <div class="list-view-wrapper"> <!– 表格插槽 –> <div style="height: 100%" v-if="listViewType === 'table'"> <slot name="tableWidget" /> </div> <!– 卡片插槽 –> <div v-if="listViewType === 'card'"> <slot name="cardWidget"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: ['listViewType', 'onChangeViewType'], inheritAttrs: false, setup(props, context) { const onChangeViewType = (listViewType) => { props.onChangeViewType(listViewType); }; } }); </script> 这样一来,我们就定义好了视图容器,接下来就是通过自定义layout的方式注册该容器 layout注册 import { registerLayout, ViewType } from '@kunlun/dependencies'; registerLayout( `<view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" cols="4" slot="search"/> </view> </pack> <pack widget="group" slot="tableGroup" style="position: relative"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="TableWithCardViewWidget"> <template slot="tableWidget"> <element widget="table" slot="table" datasource-provider="true"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" />…

    2024年10月16日
    2.3K01
  • 组件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.2K00

Leave a Reply

登录后才能评论