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

相关推荐

  • 左树右表默认选择第一行

    import { BaseElementWidget, Widget, SPI, ViewType, TableSearchTreeWidget } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'tree', model: '改成当前视图的模型' }) ) export class CustomTableSearchTreeWidget extends TableSearchTreeWidget { protected hasExe = false; @Widget.Watch('rootNode.children.length') protected watchRootNode(len) { if (len && !this.hasExe) { this.hasExe = true; const firstChild = this.rootNode?.children?.[0]; if (firstChild) { this.onNodeSelected(firstChild); this.selectedKeys = [firstChild.key]; } } } }

    2024年11月26日
    75300
  • 如何关闭table的checkbox?

    需要修改xml的配置 将xml中的fields改成table,并将配置加上 // 原来的写法 <template slot=”fields” > … </template> // 配置后的写法 <template slot=”table” checkbox=”false”> … </template>

    2023年11月1日
    35700
  • 前端自定义请求入门版

    在开发过程中,为了满足业务场景、增加灵活性,前端自定义请求不可避免。下面将会从——自定义 mask、自定义表格(表单等)、自定义字段三个实际场景的角度,介绍自定义请求。这篇文章把请求都写在了 ts 中,这样便于继承重写,如果不习惯 ts 的写法,把请求写在 vue 里也是可以的。 1. 自定义 mask mask 组件通常会有一个特点:在不同页面不同模型或不同应用下都展示,与业务模型无关,且往往只需要请求一次。同时可能有精确控制请求体大小的需求,这就很适合采取手写 GraphQL 的方式。 例如,我要重写顶部 mask 中的用户组件,展示用户信息。这个请求就只需请求一次,而且不需要复用,就很适合手写 GraphQL。 这里继承平台的用户组件,然后在代码中写死 GraphQL 发起请求。但是 GraphQL 语句怎么拼呢?我们可以去默认页面,打开浏览器控制台,找到相应的请求,把 GraphQL 语句复制出来,这里复制下默认的用户请求。 http.query 参数的构造、相应结果的获取都能从请求中得到。可以看到我这里精简了请求,只取了用户名。 TS import { SPI, UserWidget, MaskWidget, Widget, http } from '@kunlun/dependencies'; import Test from './Test.vue'; @SPI.ClassFactory(MaskWidget.Token({ widget: 'user' })) export class TestWidget extends UserWidget { public initialize(props) { super.initialize(props); this.setComponent(Test); return this; } // 添加响应式注解,这样能在 vue 中接受到 ts 中的变量 @Widget.Reactive() public testUserInfo: { pamirsUser: { name: string } } | undefined; public async queryUser() { const query = ` { topBarUserBlockQuery { construct(data: {}) { pamirsUser { name } } } } `; const result = await http.query('user', query); this.testUserInfo = result.data['topBarUserBlockQuery']['construct'] as { pamirsUser: { name: string } }; } public mounted() { this.queryUser(); } } VUE <template> <div class="Test"> {{ testUserInfo }} </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ name: 'Test', props: ['testUserInfo'] }); </script> 效果如下: 2. 自定义表格(表单)等视图元素组件 2-1. 自定义表格 2-1-1. 自定义表格自动获取数据 Oinone 提供了前端组件的默认实现。所以生成默认页面的时候,请求数据都是通的,可以看到表格、表单、表单里的字段等组件数据都是能回填的。所以这里继承平台的表格组件,就有了平台表格自动获取数据的能力。 TS import { BaseElementWidget, SPI, TABLE_WIDGET, TableWidget, ViewType } from '@kunlun/dependencies'; import Test from './Test.vue'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget:…

    2025年4月17日
    10700
  • 前端低无一体使用教程

    介绍 客户在使用oinone平台的时候,有一些个性化的前端展示或交互需求,oinone作为开发平台,不可能提前预置好一个跟客户需求一模一样的组件,这个时候我们提供了一个“低无一体”模块,可以反向生成API代码,生成对应的扩展工程和API依赖包,再由专业前端研发人员基于扩展工程(kunlun-sdk),利用API包进行开发并上传至平台,这样就可以在界面设计器的组件里切换为我们通过低无一体自定义的新组件。 低无一体的具体体现 “低”— 指低代码,在sdk扩展工程内编写的组件代码 “无”— 指无代码,在页面设计器的组件功能处新建的组件定义 低无一体的组件跟直接在自有工程内写组件的区别? 低无一体的组件复用性更高,可以在本工程其他页面和其他业务工程中再次使用。 组件、元件介绍 元件 — 指定视图类型(viewType) + 指定业务类型(ttype)字段的个性化交互展示。组件 — 同一类个性化交互展示的元件的集合。组件是一个大一点的概念,比如常用的 Input 组件,他的元件在表单视图有字符串输入元件、密码输入元件,在详情和表格展示的时候就是只读的,页面最终使用的其实是元件。通过组件+ttype+视图类型+是否多值+组件名可以找到符合条件的元件,组件下有多个元件会根据最优匹配规则找到最合适的具体元件。 如何使用低无一体 界面设计器组件管理页面添加组件 进入组件的元件管理页面 点击“添加元件” 设计元件的属性 这里以是否“显示清除按钮”作为自定义属性从左侧拖入到中间设计区域,然后发布 点击“返回组件” 鼠标悬浮到卡片的更多按钮的图标,弹出下拉弹出“低无一体”的按钮 在弹窗内点击“生成SDK”的按钮 生成完成后,点击“下载模板工程” 解压模板工程kunlun-sdk.zip 解压后先查看README.MD,了解一下工程运行要点,可以先运行 npm i 安装依赖 再看kunlun-plugin目录下已经有生成好的组件对应的ts和vue文件 下面在vue文件内增加自定义代码,可以运行 npm run dev 在开发模式下调试看效果 <template> <div class="my-form-string-input"> <oio-input :value="realValue" @update:value="change" > <template #prepend>MyPrepend</template> </oio-input> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import { OioInput } from '@kunlun/vue-ui-antd'; export default defineComponent({ name: 'customField1', components: { OioInput }, props: { value: { type: String }, change: { type: Function }, }, setup(props) { const realValue = ref<string | null | undefined>(props.value); return { realValue }; } }); </script> <style lang="scss"> .my-form-string-input { border: 1px solid red; } </style> 确定改好代码后运行 npm run build,生成上传所需的js和css文件 可以看到 kunlun-plugin目录下多出了dist目录,我们需要的是 kunlun-plugin.umd.js 和 kunlun-plugin.css 这2个文件 再次回到组件的“低无一体”管理弹窗页面,上传上面生成的js和css文件,并点击“确定”保存,到这里我们的组件就新增完成了。 下面我们再到页面设计器的页面中使用上面设计的组件(这里的表单页面是提前准备好的,这里就不介绍如何新建表单页面了) 将左侧组件库拉直最底部,可以看到刚刚新建的组件,将其拖至中间设计区域,我们可以看到自定义组件的展示结果跟刚刚的代码是对应上的(ps: 如果样式未生效,请刷新页面查看,因为刚刚上传的js和css文件在页面初始加载的时候才会导入进来,刚刚上传的动作未导入新上传的代码文件),再次点击设计区域中的自定义组件,可以看到右侧属性设置面板也出现了元件设计时拖入的属性。 最后再去运行时的页面查看效果,与代码逻辑一致! 注意事项 为什么我上传了组件后页面未生效? 检查前端启动工程的低无一体是否配置正确

    2023年11月6日
    1.4K00
  • 如何自定义点击导出动作绑定指定模板

    介绍 平台默认的导出会打开弹窗,然后在弹窗内的视图选择是用模板方式导出还是选字段导出,但是有时候有部分场景希望点击导出动作后直接进入导出流程,导出指定的某个模板,我们可以通过覆写打开弹窗的动作来实现该功能。 本文档参考了 表格页自定义按钮如何获取搜索区域的查询条件 代码示例 以下代码大部分场景只需要修改其中excelTplName更换模板即可,另外如何想增加复用性,还可以将该属性改为从元数据的配置中获取。 import { ActionType, BaseActionWidget, BaseElementListViewWidget, BooleanHelper, ClickResult, Condition, ExcelExportTask, FILE_MODULE_NAME, getSessionPath, GraphqlHelper, http, IQueryPageResult, ISort, queryDslWidget, ReturnPromise, RuntimeServerAction, ServerActionWidget, SPI, SubmitValue, SYSTEM_MODULE_NAME, translateValueByKey, UrlHelper, ViewActionTarget, Widget } from '@kunlun/dependencies'; import { OioNotification } from '@kunlun/vue-ui-antd'; @SPI.ClassFactory( BaseActionWidget.Token({ actionType: [ActionType.View], target: [ViewActionTarget.Dialog], model: 'ys0328.k2.Model0000000453', name: 'internalGotoListExportDialog' }) ) export class DemoExportActionWidget extends ServerActionWidget { /** * excel导出模板名称 * @protected */ protected excelTplName = '演示抽屉跳转链接导出'; /** * 导出任务的模型编码 * @protected */ protected exportTaskModel = 'excelExportTask'; /** * 导出任务的方法 * @protected */ protected exportTaskFun = 'createExportTask'; /** * * 是否是同步导出 */ @Widget.Reactive() protected get syncExport() { return BooleanHelper.isTrue(this.getDsl().sync) || !true; } protected async executeAction(action: RuntimeServerAction, parameters: SubmitValue): Promise<ClickResult> { const workbookId = await this.getWorkbookId(); if (!workbookId) { return false; } let task = { workbookDefinition: { id: workbookId } } as ExcelExportTask; // 从平台内置的方法获取搜索区域的条件 const { condition } = this.getSearchRsqlAndQueryParams(); // 排序规则 let sortList = [] as ISort[]; const baseViewWidget = Widget.select(this.rootHandle); const listViewWidget = queryDslWidget(baseViewWidget?.getChildrenInstance(), BaseElementListViewWidget); if (listViewWidget) { sortList = listViewWidget.sortList; } return this.export(task, condition, sortList); } protected getUploadBodyGql(id: string, condition: string | Condition, sortList:…

    2024年10月9日
    68200

Leave a Reply

登录后才能评论