组件SPI机制(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为根节点的树:

image.png

``` mermaid
graph TD
BaseFieldWidget ---> FormStringFieldWidget
BaseFieldWidget ---> FormStringMultiFieldWidget
FormStringFieldWidget ---> FormStringHyperlinksFieldWidget
```

树的构建

上述形成的组件树实际并非真实的存储结构,真实的存储结构是通过维度进行存储的,如下图所示:

(圆角矩形表示维度上的属性和值,矩形表示对应的组件)

image.png

``` mermaid
graph TD
viewType([viewType: ViewType.Form]) --->
ttype([ttype: ModelFieldType.Strng]) --->
multi([multi: true]) & widget([widget: 'Hyperlinks'])

direction LR
ttype ---> FormStringFieldWidget
multi ---> FormStringMultiFieldWidget
widget ---> 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": "Hyperlinks",
    "model": "demo.DemoModel",
    "viewName": "演示模型form",
    "name": "name"
}

通过上述定义的对象,我们在存储结构中按照指定的维度顺序进行查找,就可以获取到我们需要的组件了。

查找过程简述:

  • 匹配第一层viewType为FORM或包含FORM的节点
  • 匹配第二层ttype为STRING或包含STRING的节点(此时,FormStringFieldWidget被插入到待返回队列首位)
  • 匹配第三层multi为false的节点。(此时,没有任何节点匹配,继续匹配当前层)
  • 匹配第三层widget为Hyperlinks的节点。(此时,FormStringHyperlinksFieldWidget被插入到待返回队列首位)
  • 第四层为空,不再继续向下查找。
  • 返回待返回队列首项。

特殊的默认组件

BasePackWidget为例,平台提供的DefaultGroupWidget组件是这样注册的:

@SPI.ClassFactory(BasePackWidget.Token({}))
export class DefaultGroupWidget extends BasePackWidget {
  ......
}

该组件中不包含任何维度属性,我们无法将它添加到树中的任何一个节点,所以我们称这个组件在BasePackWidget这个SPIToken中为默认组件,任何SPIToken中的默认组件有且仅有一个。

因此,在dsl中,我们可以用如下方式直接使用这个默认组件:

<view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel">
    <pack>
        <field data="name" widget="Hyperlinks" />
    </pack>
</view>

通常情况下,我们希望dsl尽可能提供足够清楚的描述,因此,有时也可能看到这样的dsl模板:

<view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel">
    <pack widget="group">
        <field data="name" widget="Hyperlinks" />
    </pack>
</view>

由于平台并没有提供widget="group"这个组件,因此,这两个dsl模板的最终执行结果是完全一致的。

精确匹配和模糊匹配

当我们希望一个组件可以在多个视图中共用时,我们通常使用这样的注册方式:

维度:

  • 视图类型:表单(FORM)、搜索(SEARCH)
  • 字段业务类型:String类型

说明:

  • 该字段组件可以在表单(FORM)搜索(SEARCH)视图中使用
  • 并且该字段的业务类型是String类型
@SPI.ClassFactory(
  BaseFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.String
  })
)
export class FormStringFieldWidget extends BaseFieldWidget {
  ......
}

这样,我们就可以在多个视图中使用同一个组件。

母版中的标签

母版中的所有标签均使用MaskWidget作为SPIToken组件。
(旧版使用ViewWidget作为SPIToken组件,使用方式与下方描述完全不同,可略过组件注册相关内容)

维度:

  • dslNodeType:xml中的标签
  • widget:组件名称

标签组件概览

为了提供更好的灵活性,平台提供的所有标签组件,均支持classstyle属性,在无法满足业务需求的情况下,可以使用这些特性进行处理。

标签 描述
mask 母版根标签
multi-tabs 多选项卡
header 顶部栏
container 水平布局容器
sidebar 侧边栏
content 主内容区
block 块(div)
breadcrumb 面包屑
widget 母版通用组件

母版通用组件

母版通用组件全部使用widget作为标签,使用widget属性查找对应的组件。

例如:

<widget widget="app-switcher" />
标签 功能
app-switcher 应用切换组件
divider 分割线
notification 用户消息通知组件
language 多语言切换组件
user 用户信息展示组件
nav-menu 导航菜单
main-view 主视图组件;用于渲染布局和DSL等相关内容

默认母板

<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>

母版标签注册

例如mask标签的注册:

@SPI.ClassFactory(MaskWidget.Token({ dslNodeType: 'mask' }))
export class MaskRootWidget extends MaskWidget {
  ......
}

在母版中是这样使用的:

<mask>
  ......
</mask>

例如multi-tabs标签的注册:

@SPI.ClassFactory(MaskWidget.Token({ dslNodeType: 'multi-tabs' }))
export class MultiTabsWidget extends MaskWidget {
  ......
}

在母版中是这样使用的:

<mask>
  <multi-tabs />
  ......
</mask>

通用母版组件的注册

例如widget="main-view"这样的组件注册:

@SPI.ClassFactory(MaskWidget.Token({ widget: 'main-view' }))
export class MainViewWidget extends MaskWidget {
  ......
}

在母版中是这样使用的:

<mask>
  <widget widget="main-view" />
  ......
</mask>

替换母版中的平台内置组件

当使用与平台内置组件注册条件一致的SPIToken进行注册时,将实现内置组件的替换。

以多选项卡(multi-tabs)为例:

@SPI.ClassFactory(MaskWidget.Token({ dslNodeType: 'multi-tabs' }))
export class CustomMultiTabsWidget extends MaskWidget {
  ......
}

标签组件和通用母版组件的区别

使用标签组件时,该组件将完全控制当前组件的渲染逻辑,与框架本身的渲染逻辑是完全一致的。

使用通用模板组件时,该组件将被默认包裹在一个特定div标签下。

下面我们通过示例来了解一下。

先定义一个vue组件,在ts组件中通过不同的注册方式,将获得不同的渲染结果。

CustomMaskHelloWorld.vue
<template>
  <div>hello world !</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'CustomMaskHelloWorld'
});
</script>

使用标签组件

CustomMaskHelloWorldWidget.ts
@SPI.ClassFactory(MaskWidget.Token({ dslNodeType: 'custom-mask-widget' }))
export class CustomMaskHelloWorldWidget extends MaskWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(CustomMaskHelloWorld);
    return this;
  }
}
<mask>
  <custom-mask-widget />
  ......
</mask>
最终渲染结果
<div class="k-layout-mask">
  <div>hello world !</div>
  ......
</div>

使用通用母版组件注册

CustomMaskHelloWorldWidget.ts
@SPI.ClassFactory(MaskWidget.Token({ widget: 'custom-mask-widget' }))
export class CustomMaskHelloWorldWidget extends MaskWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(CustomMaskHelloWorld);
    return this;
  }
}
<mask>
  <widget widget="custom-mask-widget" />
  ......
</mask>
最终渲染结果
<div class="k-layout-mask">
  <div class="k-layout-widget">
    <div>hello world !</div>
  </div>
  ......
</div>

布局中的标签

标签 Token组件 维度(dsl属性) 可选项 描述
view BaseView type ViewType[Enum] 视图标签;主要用于元数据隔离;
pack BasePackWidget viewType
widget
inline
ViewType[Enum]
[String]
[Boolean]
容器类组件标签
element BaseElementWidget viewType
widget
inline
ViewType[Enum]
[String]
[Boolean]
任意元素组件标签

默认布局

平台根据不同的视图类型内置了一些默认布局模板。参考文档

DSL中的标签

标签 Token组件 维度(dsl属性) 可选项 描述
view BaseView type ViewType[Enum] 视图标签;主要用于元数据隔离;
field BaseFieldWidget viewType
ttype
multi
widget
model
viewName
name
ViewType[Enum]
ModelFieldType[Enum]
[Boolean]
[String]
[String]
[String]
[String]
字段元数据标签
action BaseActionWidget viewType
actionType
target
widget
viewName
model
name
ViewType[Enum]
ActionType[Enum]
ViewActionTarget[Enum]
[String]
[String]
[String]
[String]
动作元数据标签
pack BasePackWidget viewType
widget
inline
ViewType[Enum]
[String]
[Boolean]
容器类组件标签
element BaseElementWidget viewType
widget
inline
ViewType[Enum]
[String]
[Boolean]
任意元素组件标签

在上表中,我们可以看到,pack组件element组件的获取维度虽然是类似的,但我们依然将其进行了拆分。原因在于,当pack组件中不包含任何元素或所有元素都隐藏时,我们希望pack组件可以同时隐藏,但element组件则无法确定是否需要这样的特性,因此element组件默认没有进行这样的隐藏处理。

写在最后

在渲染母版布局DSL时,组件SPI机制使得动态组件可以按照设计好的维度进行获取,使得动态渲染可以按照一定的规范进行二次改造。

为了使得动态渲染可以更加灵活,我们提供了自定义标签注册自定义创建SPIToken等功能,开发者们完全可以根据自己的理解设计出一套全新的模板语法,以弥补我们在平台内置方面的不足。

HTML标签、Vue组件,都是按照标签进行简单的获取组件,这在框架层面来说是完全足够的。但美中不足的是,如果全部使用标签形式来设计我们的模板语法,会导致模板语法难以阅读和理解。每个人都需要知道这个标签背后的实现逻辑才可能清楚的理解这段模板所描述的主要内容,再加上开发人员的独特理解风格,最终必然会导致对这段模板语法的解释只能由为数不多的开发人员理解。为了避免这些语法理解上的差异化,使用被设计好的维度来保证每个人的理解是尽可能保持一致的。

例如一个“糟糕”的模板:

<view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel">
    <field data="id" invisible="true" />
    <my-widget1>
        <field data="name" />
    </my-widget1>
    <my-widget2>
        <field data="isEnabled" />
    </my-widget2>
</view>

这段模板很难推断my-widget1my-widget2这两个组件所承担的主要功能。

但如果使用这样的模板进行描述:

<view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel">
    <field data="id" invisible="true" />
    <pack widget="my-widget1">
        <field data="name" />
    </pack>
    <pack widget="my-widget2">
        <field data="isEnabled" />
    </pack>
</view>

我们虽然同样无法确定这两个组件所承担的具体功能,但pack标签的特性将告诉我们,这两个组件至少是一个容器类的组件,它本身所承担的是描述表单布局相关功能的组件。

不过,显而易见的是,虽然我们提供了一整套标准的模板语法来描述一个页面是如何呈现的,但仍然无法阻止开发人员在实现一个具体功能的组件时,一定要按照设计规范来编码。

因此,我们希望所有的开发者们,可以遵循这套设计规范来定义组件以及实现组件。

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

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

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

相关推荐

  • 前端环境和启动前端工程

    本节核心是带大家直观的感受下我们上节构建的demo模块,并搭建前端环境为后续学习打下基础 环境准备 配置NPM源 npm config set registry http://nexus.shushi.pro/repository/kunlun/ 登录NPM源账号 npm login –registry "http://nexus.shushi.pro/repository/kunlun/" # username、password、email 获取方式: # 1、请见oinone开源社区群公告,也可以联系oinone合作伙伴或服务人员; # 2、参考数式发过去的部署包(部署.zip)中的账号说明:docker-mvn-npm账号.md npm info underscore 环境准备参考 [前端环境准备Mac版本]https://doc.oinone.top/oio4/9225.html [前端环境准备Windows版本]https://doc.oinone.top/oio4/9226.html 启动前端工程 1、下载前端工程本地运行 [ss-front-modules.zip]ss-front-modules 2、解压下载后的工程,可以查看README.MD快速上手指南; 找到vue.config.js文件,修改devServer.proxy.pamirs.target为后端服务的地址和端口 const WidgetLoaderPlugin = require('@kunlun/widget-loader/dist/plugin.js').default; const Dotenv = require('dotenv-webpack'); module.exports = { lintOnSave: false, runtimeCompiler: true, configureWebpack: { module: { rules: [ { test: /\.widget$/, loader: '@kunlun/widget-loader' } ] }, plugins: [new WidgetLoaderPlugin(), new Dotenv()], resolveLoader: { alias: { '@kunlun/widget-loader': require.resolve('@kunlun/widget-loader') } } }, devServer: { port: 8081, disableHostCheck: true, progress: false, proxy: { pamirs: { // 支持跨域 changeOrigin: true, // 改成本地后端对应的IP和端口; 本地后端未启动的情况也可改成无代码后端IP和端口 target: 'http://192.168.0.121:8190' } } } }; 3、 安装依赖和运行在工程目录ss-front-modules下执行 # 安装依赖 npm i # 运行 npm run dev 4、若安装失败 检查本地node、npm、vue对应的版本 5、 如果启动报错 清除node_modules后重新 npm i mac清除命令:npm run cleanOs windows清除命令: npm run clean 注:要用localhost域名访问,.env文件这里也要改成localhost。如果开发中一定要出现前后端域名不一致,老版本Chrome会有问题,修改可以请参https://www.cnblogs.com/willingtolove/p/12350429.html。或者下载新版本Chrome 进入前端工程ss-front-modules文件目录下,执行 npm run dev,最后出现下图就代表启动成功 6、使用 http://127.0.0.1:8081/login 进行访问,并用admin账号登陆,默认密码为admin 5、点击左上角进行应用切换,会进入App Finder页面,可以看到所有已经安装的应用,可以对照boot的yml配置文件看。 在后续的学习过程中我们会不断完善示例中的模块。至此恭喜您,前端工程已经启动完成。 示例工程分层说明 # ss-boot 不做业务研发,只做包的组装和依赖 # ss-oinone 与Oinone结合层,这个工程结构可以把数式Oinone的改造收口,也是业务工程依赖的核心层 # ss-admin-widget 与界面设计器无代码的结合工程,在这个工程结构里可以把组件放在无代码平台上使用 # ss-project 模拟的项目工程,做某个项目的个性化开发

    2024年5月28日
    2.6K00
  • 自定义字段的数据联动

    某种情况下,开发人员期望自定以的字段发生变化后,需要修改其他的字段,这篇文章从两个维度来讲解如果处理数据的联动 界面设计器配置 1: 在界面设计器页面中的的组件区域找到自定义的字段,设计元件 2: 在模型区域,搜索提交方式,如果找到了,就把该字段拖拽进来, 如果找不到,就在元件中的组件区域,拖拽一个文本字段,按照下面的配置进行配置,然后保存 图一是找到了对应的字段图二是找不到对应的字段 【图一】 【图二】 图二的字段编码必须是constructDataTrigger 3: 从模型区搜索联动函数,将其拖拽进来 3: 从模型区搜索提交数据,将其拖拽进来4: 从模型区搜索提交字段,将其拖拽进来 5: 发布 (记得刷新页面哦) 最后再到对应的设计器页面,选中该字段,进行配置 提交方式为blur或者change , 需要开发者手动调用该方法 this.blur()或者this.change(value) // 字段对应的ts文件 class MyField extends FormFieldWidget { onChangeValue(val) { // this.change(val) // this.blur() } } 联动函数就是要调用的后端函数 提交数据分为:变更字段 -> 发生变化后的字段当前视图字段 -> 当前视图所有的字段指定字段 -> 指定字段,如果配置的指定字段,那么提交字段的配置就要输入对应的字段 代码配置 平台也支持通过代码的方式修改字段 // 字段对应的ts文件 class MyField extends FormFieldWidget { onChangeValue(val) { // 修改字段本身的值 this.change(val) // 修改其他字段的值 this.formData.otherField = 'value' this.reloadFormData$.subject.next(true); } }

    2023年11月9日
    2.1K00
  • 列表视图、卡片视图切换

    在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式. 这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格跟卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。 我们用资源模块下面的国家菜单来实现这么一个功能这是对应的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
  • 表格主题配置(v4)

    TableThemeConfig /** * 表格主题配置 */ export interface TableThemeConfig { border: boolean | string; stripe: boolean; isCurrent: boolean; isHover: boolean; /** * 表格列主题配置 */ column: Partial<TableColumnThemeConfig>; } /** * 表格列主题配置 */ export interface TableColumnThemeConfig { /** * <h3>最小宽度</h3> * <ul> * <li>boolean: enabled column width auto compute</li> * <li>number: using css width (default: px)</li> * <li>string: using css width</li> * <li> * object: auto compute width for label by default function * <ul> * <li>min: min min width (default: 120)</li> * <li>max: max min width (default: 432)</li> * <li>chineseWidth: chinese width (default: 14 -> fontSize: 14px)</li> * <li>otherWidth: non chinese width (default: 9 -> fontSize: 14px)</li> * <li>sortableFixWidth: sortable handle width (default: 40)</li> * <li>nonSortableFixWidth: non sortable fix width (default: 22)</li> * </ul> * </li> * <li>function: auto compute width for label by function</li> * </ul> */ minWidth: boolean | number | string | Partial<TableColumnMinWidthComputeConfig> | TableColumnMinWidthComputeFunction; /** * 操作列 */ operation: { /** * 宽度 (default: 165) */ width?: number | string; /** * 最小宽度 (default: 120) */ minWidth?: number | string; }; } export interface TableColumnMinWidthComputeConfig { min: number;…

    2023年11月1日
    1.4K00
  • 弹窗或抽屉表单视图rootRecord获取不到对应的数据

    在平台默认的实现中,rootRecord 代表的是根视图的数据。比如,在表格页面点击按钮打开了弹窗,弹窗里面包含一个表单视图,但是该视图获取 rootRecord 却是最外层的视图数据。 如果期望 rootRecord 数据是弹窗的视图数据,需要手动修改表单的 rootRecord。下面的代码演示了如何重写 rootData 以确保其数据是弹窗的数据: @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Form, widget: 'MyCustomFormWidgetFormWidget' }) ) export class MyCustomFormWidgetFormWidget extends FormWidget { @Widget.Reactive() @Widget.Provide() public get rootData(): any[] | undefined { return this.activeRecords; } } 上述代码重写了 rootData,这样就可以确保 rootData 的数据是弹窗的数据。 接下来就是注册: registerLayout( ` <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="MyCustomFormWidgetFormWidget" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> `, { viewType: ViewType.Form, model: '弹窗模型', viewName: '弹窗视图名称' } )

    2023年11月13日
    1.9K00

Leave a Reply

登录后才能评论