前端自定义字段与视图最佳方案

自定义视图获取数据

在某些情况下,oinone 提供的默认视图无法满足需求,这时我们就需要自定义视图。通常,虽然视图的 UI 不足以满足要求,但数据结构是不变的。此时,重点是修改页面 UI,数据的请求与回填可以利用平台默认的能力。

如何实现?

  1. 界面设计器的使用
    1. 你可以通过界面设计器先配置页面,平台在运行时会根据设计器生成对应的 GraphQL 请求,并自动回填数据。
  2. 视图的数据结构
    1. 视图的数据类型分为可以分为 Object 跟 List
      1. Object 代表当前视图的数据结构是对象
      2. 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 lang="ts">
export default defineComponent({
  props: {
    dataSource: {
      // 当前列表的数据
      type: Object,
      default: () => ({})
    }
  }
});
</script>

自定义layout

// 原始的layout模版
<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" slotSupport="field">
                <xslot name="searchFields" slotSupport="field" />
            </element>
        </view>
    </pack>
    <pack widget="group" slot="tableGroup">
        <element widget="actionBar" slot="actionBar" slotSupport="action">
            <xslot name="actions" slotSupport="action" />
        </element>
        <element widget="table" slot="table" slotSupport="field">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" slotSupport="field" />
            <element widget="rowActions" slot="rowActions" slotSupport="action" />
        </element>
    </pack>
</view>

//自定义的layout模版
<view type="TABLE">
    <pack widget="group">
        <view type="SEARCH">
            <element widget="search" slot="search" slotSupport="field">
                <xslot name="searchFields" slotSupport="field" />
            </element>
        </view>
    </pack>
    <pack widget="group" slot="tableGroup">
        <element widget="actionBar" slot="actionBar" slotSupport="action">
            <xslot name="actions" slotSupport="action" />
        </element>
        <element widget="custom-table" slot="table" slotSupport="field">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" slotSupport="field" />
            <element widget="rowActions" slot="rowActions" slotSupport="action" />
        </element>
    </pack>
</view>

其实就是把 widget="table" 改成 widget="custom-table"

以上代码展示了如何继承对应的 widget,平台会自动调用接口获取数据。如果你需要自定义接口调用,请参考然后参考这篇文章,调用接口

自定义字段获取数据、提交数据

在 oinone 中,常见的自定义字段分为两种,一种是表单字段,一种是列表字段。

表单字段

1.  所有的表单字段都有 value 属性,用来存储当前字段对应的值
2.  所有的表单字段都有 formData 属性,用来获取其他字段的值,也可以修改其他字段的值
3.  所有的表单字段都有 change 方法,用来修改当前字段对应的值

示例:

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.String
  })
)
export class CustomFormFieldWidget extends FormFieldWidget {
  mounted() {
    console.log(this.value); // 获取当前字段的值
    console.log(this.formData); // 获取当前表单的值
    this.change('hello'); // 修改当前字段的值
  }
}

Vue 组件:

<template>
  <div @click="change('hi')">{{ value }}</div>
</template>
<script lang="ts">
export default defineComponent({
  props: {
    value: {
      type: String
    },
    change: {
      type: Function
    }
  }
});
</script>

列表字段

  1. 所有的列表字段都有 dataSource 属性,用来获取当前列表的数据
  2. 所有的列表字段中的 renderDefaultSlot 方法,用来渲染单元格组件

示例:

@SPI.ClassFactory(
  BaseFieldWidget.Token({
    viewType: ViewType.Table,
    ttype: [ModelFieldType.String, ModelFieldType.Phone, ModelFieldType.Email]
  })
)
export class CustomTableFieldWidget extends BaseFieldWidget {
  @Widget.Method()
  public renderDefaultSlot(context): VNode[] | string {
    const currentValue = this.compute(context) as string[];
    return [createVNode(TableVue, { value: currentValue })];
  }
}

Vue 组件:

// Table.vue
<template>
  <div>{{ value }}</div>
</template>
<script lang="ts">
export default defineComponent({
  props: {
    value: {
      type: String
    }
  }
});
</script>

通过这些自定义实现,你可以灵活地控制视图和字段的数据获取与修改,并且能够利用平台的 GraphQL 能力来实现更简便的数据操作。

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

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

(2)
汤乾华的头像汤乾华数式员工
上一篇 2024年10月16日 pm4:56
下一篇 2024年10月17日 pm9:13

相关推荐

  • oinone的GraphQL使用指南

    如果之前没了解过GraphQL,可以先查看GraphQL的文档 为什么oinone要选用GraphQL? 我们先看一下oinone独特的元数据设计 介绍信息来源于Oinone 7天从入门到精通,如提示无权限,则需要申请 再看一下GraphQl的介绍 我们可以看出,GraphQL提供的特性可以满足我们对元数据的描述需求,因此我们选用GraphQL。 相关工具推荐 可视化gql请求工具: 官方下载地址 oinone工具包-win版 oinone工具包-mac版 模拟登录请求 点击“Refresh Schema”按钮手动同步后端的gql文档数据 点击“show Documentation”按钮查看gql文档,可以在搜索框内输入关键字查询相关文档

    2023年11月1日
    1.3K00
  • 自定义视图组件(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4) 什么是视图组件 我们将一个视图中提供数据源的组件称为视图组件。 下面,我们将根据提供的示例布局进行进一步介绍。 示例布局(默认表格视图布局) <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: 视图标签;一个视图中的所有组件将共享数据源,视图的数据源通过视图组件进行提供。(在这个示例中,该视图的数据源通过widget="table"(TableWidget)提供) pack: 容器组件标签; element: 通用元素组件标签; xslot:dsl插槽; 根据标签性质,我们可以将这个示例布局进一步简化,只留下我们目前要关注的主要内容。 <view type="TABLE"> <element widget="table" slot="table"> <xslot name="fields" /> </element> </view> 在以上示例布局中,有且仅有一个组件会向视图提供数据源,那就是widget="table"(TableWidget)这个组件。我们接下来将对这个组件进行自定义,以实现业务中所需的列表(List)数据源展示方式。 1 平台组件简介 平台提供的基础组件有如下几种: 组件名称 描述 BaseElement element标签通用组件 BaseElementViewWidget 通用视图组件 BaseElementObjectViewWidget 对象(Object)数据源通用视图组件 BaseElementListViewWidget 列表(List)数据源通用组件 平台提供的内置组件有如下几种:(均使用element标签) 组件名称 标签 视图类型 描述 TableWidget widget="table" TABLE 内置表格组件 FormWidget widget="form" FORM 内置表单组件 DetailWidget widget="detail" DETAIL 内置详情组件 GallertWidget widget="gallery" GALLERY 内置画廊组件 TreeWidget/CardCascaderWidget widget="tree/cardCascader" TREE 内置树/卡片级联组件 我们可以根据业务场景,继承不同的组件,来实现自己的业务场景。在自定义过程中,我们建议尽可能的将逻辑控制在组件内部。如果场景是唯一且确定的,也可以进行一些特殊逻辑处理。 2 场景:实现一个虚拟滚动表格(不使用分页器) 2.1 确定组件名称 widget="VirtualTable" 通过布局设置自定义组件名称 我们将原表格布局中的widget="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="VirtualTable" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> 通过DSL设置自定义组件名称 我们使用了slot="table"这个插槽,通过属性合并覆盖的方式,在DSL上直接指定我们所需要的自定义组件名称即可。 这种修改方式更适用于个别几个视图需要使用该组件的情况。 <view type="TABLE"> <template slot="table" widget="VirtualTable"> …… </template> </view> 2.2 简单实现一个基础功能的虚拟滚动表格 定义一个VirtualTable.vue文件,使用平台提供的oio-table组件。目前内部采用vxe-table封装,相关api文档 点击查看 props定义: showDataSource: 当前展示数据;通过平台内置BaseElementListViewWidget组件提供。 <template> <oio-table ref="table" border show-overflow height="400" :row-config="{ isHover: true…

    2023年11月1日
    1.2K00
  • 创建与编辑一体化

    在业务操作中,用户通常期望能够在创建页面后立即进行编辑,以减少频繁切换页面的步骤。我们可以充分利用Oinone平台提供的创建与编辑一体化功能,使操作更加高效便捷。 通过拖拽实现表单页面设计 在界面设计器中,我们首先需要设计出对应的页面。完成页面设计后,将需要的动作拖入设计好的页面。这个动作的关键在于支持一个功能,即根据前端传入的数据是否包含id来判断是创建操作还是编辑操作。 动作的属性配置如下: 前端自定义动作 一旦页面配置完成,前端需要对这个动作进行自定义。以下是一个示例的代码: @SPI.ClassFactory( ActionWidget.Token({ actionType: [ActionType.Server], model: '模型', name: '动作的名称' }) ) export class CreateOrUpdateServerActionWidget extends ServerActionWidget { @Widget.Reactive() protected get updateData(): boolean { return true; } } 通过以上步骤,我们实现了一个更智能的操作流程,使用户能够在创建页面的同时进行即时的编辑,从而提高了整体操作效率。这种创建与编辑一体化的功能不仅使操作更加顺畅,同时也为用户提供了更灵活的工作流程。

    2023年11月21日
    1.8K00
  • 自定义mutation时出现校验不过时,如何排查

    场景描述 用户在自定义接口提供给前端调用时 @Action(displayName = "注册", bindingType = ViewTypeEnum.CUSTOM) public BaseResponse register(UserZhgl data) { //…逻辑 return result; } import java.io.Serializable; public class BaseResponse implements Serializable { private String code; private String msg; public BaseResponse() { } public BaseResponse(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } gql执行时出现报错 { "errors":[ { "message":"Validation error of type SubSelectionNotAllowed: Sub selection not allowed on leaf type Object of field register @ 'zhglMutation/register'", "locations":[ { "line":3, "column":3 } ], "extensions":{ "classification":"ValidationError" } } ] } 解决方案 1.返回对象不为空时,对象必须是模型,否则无法解析返回参数2.前端调用GQL异常时,可以用Insomnia工具对GQL进行测试,根据错误提示对GQL进行修改和排查3.GQL正常情况下,执行以后可根据后端日志进行错误排查

    2023年11月1日
    1.5K00
  • 如何在表格的字段内添加动作

    介绍 在日常的业务中,我们经常需要在表格内直接点击动作完成一些操作,而不是只能在操作栏中,例如:订单的表格内点击商品名称或者里面的按钮跳转到商品详情页面,这里我们将带来大家来通过自定义表格字段来实现这个功能。 1.编写表格字段组件 组件ts文件TableBtnFieldWidget.ts import { ActionWidget, ActiveRecordExtendKeys, BaseFieldWidget, BaseListView, ModelFieldType, queryDslWidget, queryRowActionBar, RowContext, SPI, TableStringFieldWidget, BaseElementListViewWidget, ViewType, Widget } from '@kunlun/dependencies'; import { createVNode, VNode } from 'vue'; import { toString } from 'lodash-es'; import TableBtnField from './TableBtnField.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Table, ttype: [ModelFieldType.String, ModelFieldType.Text], // widget: 'StringLink', // 以下3行配置代码测试用,生产建议在界面设计器自定义组件,widget填自定义组件的api名称 model: 'resource.k2.Model0000000109', name: 'name' }) ) export class TableBtnFieldWidget extends TableStringFieldWidget { @Widget.Reactive() private get triggerActionLabel() { // 界面设计器组件内设计该属性 return this.getDsl().triggerActionLabel || '更新'; } private getTriggerAction() { return this.model.modelActions.find((a) => a.label === this.triggerActionLabel); } private getTriggerActionWidget(widgetHandle: string, draftId: string, triggerActionLabel: string): ActionWidget | undefined { const listView = Widget.select(widgetHandle) as unknown as BaseListView; const listWidget = queryDslWidget(listView?.getChildrenInstance(), BaseElementListViewWidget); if (!listWidget) { return undefined; } const rowActionBar = queryRowActionBar(listWidget.getChildrenInstance(), draftId); const actionWidget = rowActionBar?.getChildrenInstance().find((a) => (a as ActionWidget).action.label === triggerActionLabel); return actionWidget as ActionWidget; } protected clickAction(context: RowContext) { const draftId = context.data[ActiveRecordExtendKeys.DRAFT_ID] as unknown as string; const actionWidget = this.getTriggerActionWidget(this.getRootHandle()!, draftId, this.triggerActionLabel); if (!actionWidget) { console.error('未找到action', this.triggerActionLabel); return; } actionWidget.click(); } @Widget.Method() public renderDefaultSlot(context: RowContext): VNode[] | string { const value = toString(this.compute(context)); if (value) { return [ createVNode(TableBtnField,…

    2024年5月16日
    1.3K00

Leave a Reply

登录后才能评论