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

自定义视图获取数据

在某些情况下,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在登录页面提供了丰富的配置项以支持大家在业务上的个性化需求 配置方式 在manifest.js内新增以下配置选项 manifest.js文件如何配置的参考文档 runtimeConfigResolve({ login: { /** * 登录按钮label */ loginLabel: '登录', /** * 是否显示忘记密码按钮 */ forgetPassword: true, /** * 忘记密码按钮Label */ forgetPasswordLabel: '忘记密码', /** * 是否显示注册按钮 */ register: true, /** * 注册按钮Label */ registerLabel: '注册', /** * 是否显示验证码登录Tab */ codeLogin: true, /** * 账号登录Tab Label */ accountLoginLabel: '账号', /** * 验证码登录Tab Label */ codeLoginLabel: '验证码', /** * 账号登录-账号输入框Placeholder */ accountPlaceholder: '请输入账号', /** * 账号登录-密码输入框Placeholder */ passwordPlaceholder: '前输入密码', /** * 验证码登录-手机号输入框Placeholder */ phonePlaceholder: '请输入手机号', /** * 验证码登录-验证码输入框Placeholder */ codePlaceholder: '请输入验证码', } });

    2024年4月24日
    1.5K00
  • 树型表格全量加载数据如何处理

    阅读该文档的前置条件 【界面设计器】树形表格 1.前端自定义表格组件 import { ActiveRecord, BaseElementWidget, Condition, Entity, SPI, TableWidget, ViewType } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ type: ViewType.Table, widget: ['demo-tree-table'] }) ) export class TreeTableWidget extends TableWidget { // 默认展开所有层级 protected getTreeExpandAll() { return true; } // 关闭懒加载 protected getTreeLazy(): boolean { return false; } public async $$loadTreeNodes(condition?: Condition, currentRow?: ActiveRecord): Promise<Entity[]> { // 树表加载数据的方法,默认首次只查第一层的数据,这里去掉这个查询条件的参数condition,这样就会查所有层级数据 return super.$$loadTreeNodes(undefined, currentRow); } } 2. 注册layout import { registerLayout, ViewType } from '@kunlun/dependencies'; const install = () => { registerLayout( ` <view type="TABLE"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="demo-tree-table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </view> `, { viewType: ViewType.Table, model: "resource.resourceCity", // 变量,需要替换 actionName: "MenuuiMenu6f6005bdddba468bb2fb814a62fa83c6", // 变量,需要替换 } ); }; install();

    2024年8月17日
    1.1K00
  • 文件上传组件前端校验超128长度大小,不清楚怎么配置

    原因是拼上后端返回的文件全路径后超出了字段的存储长度,后端通过注解@Field.String(size=1024)修改字段的存储长度后重新运行一遍服务。

    2023年11月1日
    1.1K00
  • 前端表格复制

    我们可能会遇到表格复制的需求,也就是表格填写的时候,不是增加一行数据,而是增加一个表格。以下是代码实现和原理分析。 代码实现 在 boot 工程的 main.ts 中加入以下代码 import { registerDesignerFieldWidgetCreator, selectorDesignerFieldWidgetCreator } from '@oinone/kunlun-ui-designer-dependencies'; // 注册无代码组件,将表头分组的无代码组件,注册成字段表格组件 registerDesignerFieldWidgetCreator( { widget: 'DynamicCreateTable' }, selectorDesignerFieldWidgetCreator({ widget: TABLE_WIDGET })! ); DynamicCreateTableWidget 动态添加表格 ts 组件 import { FormO2MTableFieldWidget, Widget, DslDefinition, RuntimeView, SubmitValue, BaseFieldWidget, ModelFieldType, SPI, ViewType, ActiveRecord, uniqueKeyGenerator } from '@oinone/kunlun-dependencies'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import DynamicCreateTable from './DynamicCreateTable.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.OneToMany, widget: 'DynamicCreateTable' }) ) export class DynamicCreateTableWidget extends FormO2MTableFieldWidget { public myMetadataViewWidget: MyMetadataViewWidget[] = []; @Widget.Reactive() public myMetadataViewWidgetLength = 0; @Widget.Reactive() public myMetadataViewWidgetKeys: string[] = []; protected props: Record<string, unknown> = {}; public initialize(props) { super.initialize(props); this.props = props; this.setComponent(DynamicCreateTable); return this; } // region 创建动态表格 @Widget.Method() public async createTableWidget(record: ActiveRecord) { const index = this.myMetadataViewWidget.length; const handle = uniqueKeyGenerator(); const slotKey = `MyMetadataViewWidget_${handle}`; const widget = this.createWidget( new MyMetadataViewWidget(handle), slotKey, // 插槽名称 { subIndex: index, metadataHandle: this.metadataHandle, rootHandle: this.rootHandle, automatic: true, internal: true, inline: true } ); this.initDynamicSubview(this.props, widget); widget.setData(record); this.myMetadataViewWidgetLength++; this.myMetadataViewWidgetKeys.push(slotKey); this.myMetadataViewWidget.push(widget); } protected initDynamicSubview(props: Record<string, unknown>, widget: MyMetadataViewWidget) { const { currentViewDsl } = this; let viewDsl = currentViewDsl; if (!viewDsl) { viewDsl = this.getViewDsl(props)…

    2025年7月21日
    63400
  • 【前端】低无一体部署常见问题

    如何检查上传的SDK是否有效? 1. 在任意页面刷新后,查看是否发起【查询SDK组件】的请求。 2. 在返回的js和css列表中是否能找到在界面设计器上传的js和css文件。 3. 检查浏览器的Console中是否有组件相关报错。 4. 检查sdk中是否包含了启动工程未加入的包依赖。 启动工程包依赖:main.ts VueOioProvider( { dependencies: { vue: import('vue'), lodashEs: import('lodash-es'), antDesignVue: import('ant-design-vue'), elementPlusIconsVue: import('@element-plus/icons-vue'), elementPlus: import('element-plus'), kunlunDependencies: import('@kunlun/dependencies'), kunlunVueUiAntd: import('@kunlun/vue-ui-antd'), kunlunVueUiEl: import('@kunlun/vue-ui-el') } } ); SDK依赖:rollup.config.ts const globals = { vue: 'vue', 'lodash-es': 'lodashEs', 'ant-design-vue': 'antDesignVue', '@element-plus/icons-vue': 'elementPlusIconsVue', 'element-plus': 'elementPlus', '@kunlun/dependencies': 'kunlunDependencies', '@kunlun/vue-ui-antd': 'kunlunVueUiAntd', '@kunlun/vue-ui-el': 'kunlunVueUiEl', '@kunlun/mobile-dependencies': 'kunlunMobileDependencies', '@kunlun/vue-ui-mobile-vant': 'kunlunVueUiMobileVant' }; 上述两个文件配置的依赖和对应名称必须匹配才能在sdk上传后正常运行,否则会出现内存变量无法共享的问题。 当未发起【查询SDK组件】的请求时如何处理? 1. 在任意页面刷新后,查看manifest.js加载路径。 业务工程通常为:http://${host}:${port}/manifest.js 设计器镜像中通常为:http://${host}:${port}/config/manifest.js 2. 若未正确加载manifest.js,则在dist目录中根据请求路径添加manifest.js文件。此文件称为运行时配置文件,可点击查看参考文档。 runtimeConfigResolve({ plugins: { usingRemote: true } });

    低无一体 2023年11月1日
    1.7K00

Leave a Reply

登录后才能评论