母版-布局-DSL 渲染基础(v4)

概述

不论是母版布局还是DSL,我们统一使用XML进行定义,可以更好的提供结构化表述。

参考文档:

下面文档中未介绍到的Mask母版Layout布局,可以去数据库中base库的表base_layout_definitionbase_mask_definitiontemplate字段查看

母版

确定了主题、非主内容分发区域所使用组件和主内容分发区域联动方式的页面配置。

母版内容分为主内容分发区域与非主内容分发区域。非主内容分发区域一般包含顶部栏、底部栏和侧边栏。侧边栏可以放置菜单,菜单与主内容分发区域内容进行联动。

image.png

默认母板

<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:母版根标签
  • multi-tabs:多选项卡
  • header:顶部栏
  • container:容器
  • sldebar:侧边栏
    • nav-menu:导航菜单
  • content:主内容
    • breadcrumb:面包屑
    • block:div块
    • main-view:主视图;用于渲染布局和DSL等相关内容;

母版将整个页面的大体框架进行了描述,接下来将主要介绍布局和DSL是如何在main-view中进行渲染的。关于自定义母版组件的相关内容 点击查看

布局

布局是将页面拆分成一个一个的小单元,按照从上到下、从左到右进行顺序排列

布局主要用于控制页面中元素的展示的相对位置,原则上不建议将元数据相关内容在布局中进行使用,可最大化布局的利用率。

默认表格视图(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:视图;用于定义当前视图类型,不同的视图类型会有不同的数据交互,以及渲染不同的组件。
  • pack:容器类型相关组件。
  • element:元素组件;包含各种各样的组件,根据组件实现有不同的作用。
  • xslot:DSL插槽;用于将DSL中定义的模板分别插入到对应的槽中;

特别的,任何XML标签上的slot属性都具备DSL插槽的全部能力。当学习完DSL相关内容后,我们将会对DSL插槽有比较清晰的理解。

PS:在下面的内容中,将使用该布局进行描述。

DSL

准备工作

为了方便描述DSL和元数据之间的关系,我们需要先定义一个简单模型,这个模型里面包含字段和动作。这些通常是服务端定义的。(对服务端不感兴趣的同学可以跳过代码部分)

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)
@UxRouteButton(
        action = @UxAction(name = "redirectCreatePage", displayName = "创建", contextType = ActionContextTypeEnum.CONTEXT_FREE),
        value = @UxRoute(model = DemoModel.MODEL_MODEL, viewName = "演示模型form"))
@UxRouteButton(
        action = @UxAction(name = "redirectUpdatePage", displayName = "编辑", contextType = ActionContextTypeEnum.SINGLE),
        value = @UxRoute(model = DemoModel.MODEL_MODEL, viewName = "演示模型form"))
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
    • redirectCreatePage:创建;目标视图为"演示模型form" 跳转动作 ViewAction
    • redirectUpdatePage:编辑;目标视图为"演示模型form" 跳转动作 ViewAction
    • enable:启用 提交动作 ServerAction
    • disable:禁用 提交动作 ServerAction

根据上面的元数据内容,我们可以定义一个如下所示的DSL模板,来使用所有的元数据将其渲染到页面中:

<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="search">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="actions">
        <action name="redirectCreatePage" />
    </template>
    <template slot="fields">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="rowActions">
        <action name="redirectUpdatePage" />
        <action name="enable" />
        <action name="disable" />
    </template>
</view>

该模板中包含的标签含义:

  • view:视图;与布局模板中的view标签作用一致。
  • template:向DSL插槽中插入一个模板片段
  • field:字段元数据标签;data属性与元数据中字段API名称(field)对应。
  • action:动作元数据标签;name属性与元数据中动作API名称(name)对应。

DSL模板本质上是对布局模板的补充,它使用布局模板中已经定义好的DSL插槽,对其进行相应规则的合并替换,最终构成一个可被执行的视图模板再经过客户端渲染展示给用户。单独解释DSL模板的结构化含义是完全没有意义的。

接下来,我们会分别定义不同的DSL模板,并结合默认表格视图的布局模板进行详细说明。

模板编译

服务端模板编译基础

进入页面时,客户端将通过加载跳转动作(ViewActionLoad)接口获取对应的布局模板和DSL模板,得到经过服务端编译JSON数据,类似于如下数据结构:(DSL模板的JSON结构)

{
    "dslNodeType": "VIEW",
    "type": "TABLE",
    ......,
    "widgets": [
        {
            "dslNodeType": "TEMPLATE",
            "slot": "search",
            "widgets": [
                ......
            ]
        },
        {
            "dslNodeType": "TEMPLATE",
            "slot": "actions",
            "widgets": [
                ......
            ]
        }
    ]
}

服务端编译过程可以简单的理解为以下几个内容:(实际要复杂很多,在这里我们仅需要关注最终拿到的JSON结果即可)

  • XML转换为JSON结构
  • XML标签转换为dslNodeType属性,XML子标签转换为widgets属性,其他属性保持不变
  • field标签(字段)action标签(动作)对应的元数据补充完整。如上所示,我们仅定义了字段的data属性,返回的JSON中将包含字段动作的全部元数据。字段会补充字段类型、显示名称、是否存储等元数据属性,动作会补充动作类型、显示名称等元数据属性。

示例模板

表格视图-布局模板
<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>
表格视图-DSL模板
<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="search">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="actions">
        <action name="redirectCreatePage" />
    </template>
    <template slot="fields">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="rowActions">
        <action name="redirectUpdatePage" />
        <action name="enable" />
        <action name="disable" />
    </template>
</view>

以上所示的两个模板(布局模板DSL模板)会作为布局和DSL的模板合并相关内容介绍的演示模板。更多的布局模板点击查看

布局和DSL的模板合并

当我们从服务端获取了布局模板DSL模板对应的JSON后,客户端将对这两个JSON进行合并处理。

合并操作主要是将DSL模板中定义的根标签下定义的所有template标签,向布局模板中定义的DSL插槽进行属性合并和子节点替换。

标准合并

将上述两个模板进行合并后,将得到如下结果:(为了方便观察结构,将使用XML结构进行解释,实际运行时为JSON结构)

<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search">
                <!-- slot="search" -->
                <field data="id" invisible="true" />
                <field data="name" />
                <field data="isEnabled" />
            </element>
        </view>
    </pack>
    <pack widget="group" slot="tableGroup">
        <element widget="actionBar" slot="actionBar">
            <!-- slot="actions" -->
            <action name="redirectCreatePage" />
        </element>
        <element widget="table" slot="table">
            <element widget="expandColumn" slot="expandRow" />
            <!-- slot="fields" -->
            <field data="id" invisible="true" />
            <field data="name" />
            <field data="isEnabled" />
            <element widget="rowActions" slot="rowActions">
                <!-- slot="rowActions" -->
                <action name="redirectUpdatePage" />
                <action name="enable" />
                <action name="disable" />
            </element>
        </element>
    </pack>
</view>

由此可见,DSL模板定义的template标签下的内容被合并到了布局模板的对应位置中。接下来就可以进行模板渲染操作了。

子标签替换和属性合并

一般的,我们将处理布局模板中任何标签上的slot属性,以及xslot标签。

遵循就近原则,模板合并将取路径最短的模板进行替换和插入。

当我们需要设置表格所有字段都允许排序时,我们可以使用sortable="true"属性,设置在widget="table"组件上。

使用slot="fields"插槽是无法向widget="table"组件添加额外属性的,此时我们需要使用slot="table"这个插槽来实现我们的需求。

一个错误的DSL模板可能被定义成如下内容:(下面的示例舍弃了部分插槽内容)

<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="table" sortable="true">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="rowActions">
        <action name="redirectUpdatePage" />
        <action name="enable" />
        <action name="disable" />
    </template>
</view>

在使用相同的布局模板情况下,合并后的结果为:

<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" />
        <element widget="table" slot="table" sortable="true">
            <!-- slot="table" -->
            <field data="id" invisible="true" />
            <field data="name" />
            <field data="isEnabled" />
        </element>
    </pack>
</view>

由此可见,我们优先使用了widget="table"上的slot="table"属性作为插槽,将原本定义在widget="table"下的所有节点全部换成了在DSL模板中定义的内容,并且成功的添加了sortable="true"这一属性。

但是,由于子标签被完全替换了,因此丢弃了widget="rowActions"组件,导致slot="rowActions"中定义的内容,找不到对应的插槽,这部分DSL模板内容将被丢弃。

反向合并

由于上面的合并操作丢弃了widget="rowActions"组件,但我们的本意并不是想要丢弃这个组件,而是仍然希望能以某种规则将这部分内容继续保留。为了解决这个问题,我们提供了反向合并操作,但前提是,DOM结构必须是同一层级

基于这一特性,我们将上述模板稍加修改:(将slot="rowActions"放在slot="table"下面)

<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="table" sortable="true">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
        <template slot="rowActions">
            <action name="redirectUpdatePage" />
            <action name="enable" />
            <action name="disable" />
        </template>
    </template>
</view>

在使用相同的布局模板情况下,合并后的结果为:

<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" />
        <element widget="table" slot="table" sortable="true">
            <!-- slot="table" -->
            <field data="id" invisible="true" />
            <field data="name" />
            <field data="isEnabled" />
            <element widget="rowActions" slot="rowActions">
                <!-- slot="rowActions" -->
                <action name="redirectUpdatePage" />
                <action name="enable" />
                <action name="disable" />
            </element>
        </element>
    </pack>
</view>

由此可见,原本定义在布局模板上的widget="rowActions"被找回来了,并且正确填充了定义在DSL模板中的内容。

不足的是,由于widget="table"下面的所有内容都被替换了一次,我们无法确定原本的widget="rowActions"组件应该如何正确放置,因此使用了DSL模板中定义的位置。

这可能会带来一些弊端,比如:将某一元素必须被放在第一个节点处。针对这种情况,布局模板本来是可以做到限定作用的,但由于使用了更大范围的插槽,使得这一特性丢失。

但也带来了一些灵活性,比如:大多数情况下,某一元素需要放在第一个节点处,但在特殊情况下,该元素需要放在最后一个节点处。

我们希望插槽可以设计的尽可能的灵活,以满足各种不同场景的需求。

属性插槽

当我们希望使用属性合并,但不希望替换子标签时,我们可以使用属性插槽来解决这个问题。

当我们希望向widget="group"组件上添加title="这是一个示例标题"属性时,我们可以使用slot="tableGroup"插槽。

我们可以使用如下所示的方式来定义DSL模板:

<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="tableGroup" title="这是一个示例标题" />
    <template slot="table" sortable="true">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
        <template slot="rowActions">
            <action name="redirectUpdatePage" />
            <action name="enable" />
            <action name="disable" />
        </template>
    </template>
</view>

在使用相同的布局模板情况下,合并后的结果为:

<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" />
        </view>
    </pack>
    <pack widget="group" slot="tableGroup" title="这是一个示例标题">
        <element widget="actionBar" slot="actionBar" />
        <element widget="table" slot="table" sortable="true">
            <!-- slot="table" -->
            <field data="id" invisible="true" />
            <field data="name" />
            <field data="isEnabled" />
            <element widget="rowActions" slot="rowActions">
                <!-- slot="rowActions" -->
                <action name="redirectUpdatePage" />
                <action name="enable" />
                <action name="disable" />
            </element>
        </element>
    </pack>
</view>

由此可见,当DSL模板中的template标签下没有任何子标签时,我们仅会处理属性合并,但不会处理在原布局模板下定义的子标签。

根据这一特性,我们也可以定义这样的DSL模板来实现上面给widget="table"添加sortable="true"的需求:

<view type="TABLE" title="演示表格" name="演示模型table" model="demo.DemoModel">
    <template slot="tableGroup" title="这是一个示例标题" />
    <template slot="table" sortable="true" />
    <template slot="fields">
        <field data="id" invisible="true" />
        <field data="name" />
        <field data="isEnabled" />
    </template>
    <template slot="rowActions">
        <action name="redirectUpdatePage" />
        <action name="enable" />
        <action name="disable" />
    </template>
</view>

在使用相同的布局模板情况下,合并后的结果为:(与之前的合并结果完全一致)

<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" />
        </view>
    </pack>
    <pack widget="group" slot="tableGroup" title="这是一个示例标题">
        <element widget="actionBar" slot="actionBar" />
        <element widget="table" slot="table" sortable="true">
            <!-- slot="table" -->
            <field data="id" invisible="true" />
            <field data="name" />
            <field data="isEnabled" />
            <element widget="rowActions" slot="rowActions">
                <!-- slot="rowActions" -->
                <action name="redirectUpdatePage" />
                <action name="enable" />
                <action name="disable" />
            </element>
        </element>
    </pack>
</view>

由于属性插槽本身没有子标签,因此我们建议将所有的属性插槽配置在view标签子标签的最前面进行统一定义。

模板渲染

模板渲染是将一个包含dslNodeType属性的对象,生成对应框架的VDom对象,通过该VDom对象执行一段创建ts组件的逻辑,并通过ts组件渲染该组件对应的VDom对象

例如:使用Vue框架时,该VDom对象通常为VNode

(以下内容均围绕Vue框架进行描述,其他框架基本一致)

为了将模板渲染尽可能的接近第三方框架的渲染逻辑,我们采用了Tag(Vue组件) - Class Component(ts) - Component(Vue组件)这样的三层结构进行模板渲染的实现。

  • Tag(Vue组件):使用resolveComponent方法,将一个包含dslNodeType属性的对象,通过既定规则进行解析,获取一个Vue组件作为入口。主要处理模板标签和Vue组件之间的对应关系。
  • Class Component(ts):使用ts面向对象的特性,可以对组件逻辑进行重载、继承等处理,增加组件的灵活性。
  • Component(Vue组件):主要实现数据渲染。

具体的渲染逻辑不在此处进行介绍,该篇文章主要是为了让读者对模板以及整体框架有一个基本的概念,使用这套模板渲染的最终目的是为了让组件复用率更高,开发更加灵活。

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

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

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

相关推荐

  • 自定义组件之自动渲染(组件插槽的使用)(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4.3.0) 自定义组件简介 前面我们简单介绍过一个简单的自定义组件该如何被定义,并应用于页面中。这篇文章将对自定义组件进行详细介绍。 自定义一个带有具名插槽的容器组件(一般用于Object数据类型的视图中) 使用BasePackWidget组件进行注册,最终体现在DSL模板中为<pack widget="SlotDemo">。 SlotDemoWidget.ts import { BasePackWidget, SPI } from '@kunlun/dependencies'; import SlotDemo from './SlotDemo.vue'; @SPI.ClassFactory(BasePackWidget.Token({ widget: 'SlotDemo' })) export class SlotDemoWidget extends BasePackWidget { public initialize(props) { super.initialize(props); this.setComponent(SlotDemo); return this; } } 定义一个Vue组件,包含三个插槽,分别是default不具名插槽、title具名插槽、footer具名插槽。 SlotDemo.vue <template> <div class="slot-demo-wrapper" v-show="!invisible"> <div class="title"> <slot name="title" /> </div> <div class="content"> <slot /> </div> <div class="footer"> <slot name="footer" /> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ name: 'SlotDemo', props: { invisible: { type: Boolean, default: undefined } } }); </script> 在一个表单(FORM)的DSL模板中,我们可以这样使用这三个插槽: <view type="FORM"> <pack widget="SlotDemo"> <template slot="default"> <field data="id" /> </template> <template slot="title"> <field data="name" /> </template> <template slot="footer"> <field data="isEnabled" /> </template> </pack> </view> 这样定义的一个组件插槽和DSL模板就进行了渲染上的结合。 针对不具名插槽的特性,我们可以缺省slot="default"标签,缺少template标签包裹的所有元素都将被收集到default不具名插槽中进行渲染,则上述DSL模板可以改为: <view type="FORM"> <pack widget="SlotDemo"> <field data="id" /> <template slot="title"> <field data="name" /> </template> <template slot="footer"> <field data="isEnabled" /> </template> </pack> </view> 自定义一个数组渲染组件(一般用于List数据类型的视图中) 由于表格无法体现DSL模板渲染的相关能力,因此我们以画廊视图(GALLERY)进行演示。 先定义一个数组每一项的数据结构: typing.ts export interface ListItem { key: string; data: Record<string, unknown>; index: number; } ListRenderDemoWidget.ts import { BaseElementListViewWidget, BaseElementWidget, SPI } from '@kunlun/dependencies'; import ListRenderDemo from './ListRenderDemo.vue'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ListRenderDemo' })) export class ListRenderDemoWidget extends BaseElementListViewWidget { public initialize(props)…

    2023年11月1日
    59900
  • TableWidget 与 FormWidget 浅析

    前言:本文主要聚集于 TableWidget和 FormWidget,对两者以外的内容不做赘述。 TableWidget和 FormWidget作为一个基本的渲染模块与 Mask等不同,TableWidget与 FormWidget有着明确的目的,比如 TableWidget就是作为一个表格视图,这种名称中可以看得出来。其将表格的一系列能力聚拢,如单元格,行列选择排序,翻页等等; TableWidget 我们使用表格到底是在使用什么? 在我们讲述渲染流程前,我们需要提一个问题,在我们使用表格组件时,我们在使用什么,换而言之,我们对表格组件中关注的是什么?应该说我们更关注的是表格所展示的数据。TableWidget或者说整个低代码其实解决的就是这个问题,让我们可以很方便的展示数据。不用关心一些细枝末节 TableWidget 的渲染 TableWidget在整个渲染流程中的负责组装各种 Vue 组件所需要的核心数据或事件回调并传递给其绑定的 Vue 组件即 DefaultTable,如 allowRowClick,rowClickMode等等,这些值会作为 Props 传递给 DefaultTable,DefaultTable则一定意义上充当了一个桥接层,主要做了两件事,处理 Props, 处理 Slot,TableWidget传递给 DefaultTable的 Props 会经过 DefaultTable再次组合,创建新的 Props,同时根据当前的 Props 判断是否有必要新增一些 slot,比如 OioPagination组件是否需要渲染就取决于 Props.showPagination 的值,经过 DefaultTable的处理后, props与 slot会进一步交给 OioTable进行渲染,OioTable则会进一步聚合处理,比如如果没有 defaultSlot则进行空值的渲染,如果存在 footSlot则会构建一个包裹层去包裹它。这些组件协同完成了一个表格的结构,而我们真正关心的数据则由一个个 BaseTableColumnWidget渲染。BaseTableColumnWidget的渲染过程类似于 TableWidget,其负责组装 DefaultTableColumn渲染所需要的 props,然后交给 OioTableColumn进行实际渲染。并且会有多种基于 BaseTableColumnWidget的 widget通过重写 renderDefaultSlot,renderEditSlot,renderContentSlot,renderHeaderSlot等几个 props 实现不同状态,不同类型等组件的渲染。通过 TableWidget与 BaseTableColumnWidget相结合, Table 页面完成了主体框架与内容的渲染。而当数据完成渲染后,不可避免的会有数据交互,比如分页,排序,过滤等,这些交互都由 TableWidget与 BaseTableColumnWidget共同完成。比如排序翻页等,TableWidget会将事件作为 props 向下注入,当事件被调用,TableWidget会进行处理,比如发起请求,或者对内部数据排序等。而除了数据的展示,还有一些动作,即 Action ,Action 被触发时会按照内部的配置进行工作,比如编辑时,将获取 activeRecord,随后触发 Table 的编辑。 TableWidget就是这样去渲染出了一个完整的表格页面。能够完成数据的增删改查等操作 FormWidget FormWidget与 TableWidget一样,也是作为一个渲染模块,但是它与 TableWidget不同的是,FormWidget是作为一个表单视图,其将表单的一系列能力聚拢,如表单提交,表单校验,表单重置等等。其重点在于对数据的增改。所以在提供的能力上也有区别,比如 FormWidget没有提供翻页,排序,过滤等能力,因为这些能力属于表格的能力,而 FormWidget则更关注于表单的能力。提供了数据的存储,提交,校验等能力 Form 在渲染时流程与 Table 大同小异,其同样为三层结构 FormWidget => DefaultForm => FormFieldWidget 一层层向下渲染,不同的在于 FormWidget 更多的关注点在于维护其内部的 FormData 这是整个表单页面所围绕的东西,当页面上的控件发生变化,其变更的值会被收集到 FormData 中,并在后续中使用。同时在编辑已有数据场景下,Form 会将数据加载到 FormData,随后下放给 FormFiledWidget。 异同之处 从介绍中可以看出,Table 侧重于数据的查询展示,Form 则侧重于数据的变动处理,但是抽象的看其核心其实是同一套逻辑,即数据的存储与展示,中间或许会有对数据的某些处理,但是并不是本质上的区别,两者在核心理念上是一致的,即让使用者只需要关心数据本身,而不需要关注于繁琐的视图构造,这是整个低代码甚至前端的发展方向。

    2025年3月25日
    31800
  • 前端密码加密

    在项目开发中,我们可能会遇到自定义登录页,密码需要加密,或者是数据提交的时候,某个数据需要加密,在平台的前端中,提供了默认的全局加密 API 在 oinone 前端工程使用 // pc端工程使用 import { encrypt } from '@kunlun/dependencies'; // 移动端端工程使用 import { encrypt } from '@kunlun/mobile-dependencies'; // 加密后的密码 const password = encrypt('123456'); 其他工程使用 如果是其他工程,前端没有用到 oinone 这一套,比如小程序,或者是其他工程,可以使用下面的代码记得安装 crypto-js import CryptoJS from 'crypto-js'; const key = CryptoJS.enc.Utf8.parse('1234567890abcdefghijklmnopqrstuv'); const iv = CryptoJS.enc.Utf8.parse('1234567890aabbcc'); export const encrypt = (content: string): string => { if (typeof content === 'string' && content) { const encryptedContent = CryptoJS.AES.encrypt(content, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encryptedContent.ciphertext.toString(); } return ''; };

    2025年3月24日
    26100
  • 自定义视图部分区域渲染设计器的配置

    自定义视图与界面设计器配置对接 在日常开发中,我们经常会遇到自定义视图的需求。自定义视图不仅需要与平台机制结合,还要实现与界面设计器中配置的字段和动作的无缝对接。本文将介绍如何将自定义视图与界面设计器中配置的字段和动作的无缝对接,实现字段和动作的渲染。 用大白话来讲就是:当前页面一部分是自定义的,一部分是设计器生成的 代码地址 目录 自定义表单视图与字段、动作的结合 自定义表格视图与字段、动作的结合 自定义表单视图与字段、动作的结合 首先需要在界面设计器配置好对应界面,虽然配置的页面样式跟期望展示的 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(); } });…

    2024年9月12日
    94601
  • 界面设计器 扩展字段的查询上下文

    默认情况下oinone平台对于查询条件,只提供的当前登录用户这一个配置,但是允许开发者扩展 开发者可以在前端代码的main.ts进行扩展 import { SessionContextOptions, ModelFieldType } from '@kunlun/dependencies'; const currentDeptOption = { ttype: ModelFieldType.String, value: '$#{currentDept}', displayName: '当前登录部门', label: '当前登录部门' }; SessionContextOptions.push(currentDeptOption as any); 加上上面的代码,然后再去界面设计器,我们就会发现,多了一个配置

    2023年11月8日
    91600

Leave a Reply

登录后才能评论