自定义视图与界面设计器配置对接
在日常开发中,我们经常会遇到自定义视图的需求。自定义视图不仅需要与平台机制结合,还要实现与界面设计器中配置的字段和动作的无缝对接。本文将介绍如何将自定义视图与界面设计器中配置的字段和动作的无缝对接,实现字段和动作的渲染。
代码地址
目录
自定义表单视图与字段、动作的结合
首先需要在界面设计器配置好对应界面,虽然配置的页面样式跟期望展示的 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;
}
}
<!-- CustomForm.vue -->
<template>
<div class="custom-form-container">
<div class="custom-form-tip">自定义视图</div>
<p style="color: red">下面是通过平台机制生成的字段</p>
<field-render :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
}
},
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 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
,所以页面也会自动发起查询,并且定义了renderRowActionVNodes
和renderActionVNodes
方法,用于渲染行内动作和动作。
表单视图渲染动作
<!-- 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"
></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: {
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
}
},
render() {
const vnode = this.renderRowActionVNodes();
return createVNode(
ActionBar,
{
widget: 'rowAction',
parentHandle: uniqueKeyGenerator(),
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低代码应用平台体验