【界面设计器】自定义字段组件实战——千分位输入框

阅读之前

此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。

如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录

业务背景

用户在输入【金额】字段时,实时展示千分位格式。

业务分析

从需求来看,我们需要实现一个【千分位】组件,并且该组件允许在【表单】视图中使用。

扩展实现:该组件虽然仅要求在【表单】中使用,但也可以在【搜索】中使用完全相同的实现,因此这里我们在注册时会增加【搜索】视图,将【千分位】组件应用在【搜索】中。对于【表格】、【详情】和【画廊】来说,该组件是没有【输入】行为的展示组件,在这里我们不进行演示。

准备工作

此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。

业务模型定义

(以下仅展示本文章用到的模型字段,忽略其他无关字段。)

名称 API名称 业务类型 是否多值 长度(单值长度)
编码 code 文本 128
名称 name 文本 128
金额 money 金额 -

创建组件、元件

准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。

以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。

创建千分位组件

image.png

创建千分位元件

image.png

启动SDK工程进行组件基本功能开发

(npm相关操作请自行查看SDK工程中内置的README.MD)

关键点详解

数据交互类型的字段组件(以下简称展示组件)与仅展示类型的字段组件(以下简称交互组件)有一些差别。

通常情况下,在展示组件中仅需使用value属性即可展示相关内容。在交互组件中除了value用于展示外,还需使用changefocus以及blur处理用户输入时的基本交互逻辑。

数据交互方法主要功能说明:

  • change方法:当值发生变更时调用,根据字段相关配置将值回填至表单中。
  • focus方法:当组件获取焦点时调用,记录当前值,并在调用blur方法时进行处理。
  • blur方法:当前组件失去焦点时调用,根据focus方法记录的值,进行失焦触发逻辑的执行。

这里的三个数据交互方法仅仅是对用户行为的抽象,而并非限定其使用场景。

通常来说,这三个方法的调用顺序为:focus --> change --> blur

如在日期时间组件中,面板打开时调用了focus方法,面板值发生变更时,调用了change方法,面板关闭时调用了blur方法。

如在地图组件中,选中地图上的某个点时仅会调用change方法,用户交互上并不能体现出focusblur的行为。因此对于这个组件而言,只会有change方法被执行。

代码实现参考

PS:oio-input-number样式必须升级到4.6.x以上的最新版本才支持

Thousandth.vue
<template>
  <a-input-number
    class="oio-input-number"
    :value="inputValue"
    :formatter="formatter"
    :parser="parser"
    @update:value="change"
    @focus="focus"
    @blur="blur"
  />
</template>
<script lang="ts">
import { InputNumber as AInputNumber } from 'ant-design-vue';
import { computed, defineComponent } from 'vue';

export default defineComponent({
  name: 'Thousandth',
  components: {
    AInputNumber
  },
  props: {
    value: {
      type: [String, Number]
    },
    change: {
      type: Function
    },
    focus: {
      type: Function
    },
    blur: {
      type: Function
    }
  },
  setup(props) {
    const inputValue = computed(() => {
      return props.value;
    });

    const formatter = (value) => {
      return `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    };

    const parser = (value) => {
      return value.replace(/\$\s?|(,*)/g, '');
    };

    return {
      inputValue,
      formatter,
      parser
    };
  }
});
</script>
FormMoneyThousandthFieldWidget.ts
import { FormFieldWidget, ModelFieldType, SPI, ViewType } from '@kunlun/dependencies';
import Thousandth from './Thousandth.vue';

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: [ViewType.Form, ViewType.Search],
    ttype: ModelFieldType.Currency,
    widget: 'Thousandth',
    multi: false
  })
)
export class FormMoneyThousandthFieldWidget extends FormFieldWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(Thousandth);
    return this;
  }
}

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

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

Like (0)
张博昊's avatar张博昊数式管理员
Previous 2024年4月19日 pm7:43
Next 2024年4月19日 pm8:51

相关推荐

  • oio-cascader 级联选择

    级联选择框。 何时使用 需要从一组相关联的数据集合进行选择,例如省市区,公司层级,事物分类等。 从一个较大的数据集合中进行选择时,用多级分类进行分隔,方便选择。 比起 Select 组件,可以在同一个浮层中完成选择,有较好的体验。 API <oio-cascader :options="options" v-model:value="value" /> 参数 说明 类型 默认值 Version allowClear 是否支持清除 boolean true autofocus 自动获取焦点 boolean false changeOnSelect (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 boolean false disabled 禁用 boolean false displayRender 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" ({labels, selectedOptions}) => VNode labels => labels.join(' / ') dropdownClassName 自定义浮层类名 string – getTriggerContainer 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 Function(triggerNode) () => document.body loadData 用于动态加载选项,无法与 showSearch 一起使用 (selectedOptions) => void – maxTagCount 最多显示多少个 tag,响应式模式会对性能产生损耗 number | responsive – maxTagPlaceholder 隐藏 tag 时显示的内容 v-slot | function(omittedValues) – multiple 支持多选节点 boolean – options 可选项数据源 – placeholder 输入框占位文本 string ‘请选择’ searchValue 设置搜索的值,需要与 showSearch 配合使用 string – showSearch 在选择框中显示搜索框 boolean false tagRender 自定义 tag 内容,多选时生效 slot – value(v-model:value) 指定选中项 string[] | number[] – showSearch showSearch 为对象时,其中的字段: 参数 说明 类型 默认值 filterOption 接收 inputValue path 两个参数,当 path 符合筛选条件时,应返回 true,反之则返回 false。 function(inputValue, path): boolean 事件 事件名称 说明 回调参数 版本 change 选择完成后的回调 (value, selectedOptions) => void – search 监听搜索,返回输入的值 (value) => void – Option interface Option { value: string | number; label?: any; disabled?: boolean; children?: Option[]; // 标记是否为叶子节点,设置了 `loadData` 时有效 // 设为 `false` 时会强制标记为父节点,即使当前节点没有 children,也会显示展开图标 isLeaf?: boolean; }

    2023年12月18日
    1.5K00
  • 根据固定的接口返回数据动态控制按钮的显隐

    在项目开发中,我们经常会面临这样的情况:当前按钮的显示与隐藏需要根据后端某个接口返回的数据来决定,而无法通过权限配置进行处理。为了解决这个问题,我们可以通过自定义的方式来处理这一功能。 首先,我们需要知道当前动作是什么类型的动作,例如「服务端动作、跳转动作、打开弹窗的动作、打开抽屉的动作」。 ServerActionWidget -> 服务端动作DialogViewActionWidget -> 打开弹窗动作DrawerViewActionWidget -> 打开抽屉动作RouterViewActionWidget -> 路由跳转动作 下面是一个示例代码,演示了如何通过自定义的方式处理按钮的显示与隐藏逻辑。 import { ActionType, ActionWidget, SPI, ServerActionWidget, Widget, http } from '@kunlun/dependencies'; @SPI.ClassFactory( ActionWidget.Token({ actionType: ActionType.Server, model: 'resource.k2.Model0000000100', name: 'create' }) ) export class MyAction extends ServerActionWidget { // 当前动作是服务端动作,继承的是 ServerActionWidget @Widget.Reactive() private needInvisible = false; @Widget.Reactive() public get invisible(): boolean { return this.needInvisible; } protected mounted(): void { super.mounted(); // 模拟接口 http.query(模块名, `graphql`).then(res => { if(res) { this.needInvisible = true } }) } } 在实际应用中,我们可以调用后端接口,根据返回的数据动态修改 needInvisible 这个值,从而实现按钮的动态显示与隐藏效果。这样的设计使得按钮的显示状态更加灵活,并且能够根据后端数据动态调整,提高了系统的可定制性。

    前端 2023年11月23日
    2.0K00
  • 前端视图的元数据与数据的传递、交互

    在阅读本篇文章之前,您需要学习以下知识点: 1: 元数据 视图的元数据 在日常开发中,我们会经常遇到自定义的字段、动作、视图需要界面设计器配置的数据,这些数据可能是当前页面的字段,也有可能动作,那么如何获取呢? 视图元数据分为两种:1: 当前视图(metadataRuntimeContext)2: 根视图(rootRuntimeContext) 那么这两种类型怎么区分呢? 举个例子:1: 如果当前字段是在表单中,那么当前视图就是表单,根视图就表单的父级视图,如果只有一个表单视图,那么当前视图就是根视图。2: 如果当前视图是表单,但是表单里面有个表格,对于表格字段而言,当前视图就是表格,根视图就是表单。 当前视图的元数据(metadataRuntimeContext) 在前端,我们通过 metadataRuntimeContext 来获取视图的元数据,例如: export class CustomFormStringFieldSingleWidget extends FormStringFieldSingleWidget { protected mounted(): void { console.log(this.metadataRuntimeContext); } /** * 界面设计器配置的动作 */ @Widget.Reactive() protected get modelActions() { return this.metadataRuntimeContext.model.modelActions } /** * 界面设计器配置的字段 */ @Widget.Reactive() protected get modelFields() { return this.metadataRuntimeContext.model.modelFields } } 属性名 类型 可选性 描述 viewAction RuntimeViewAction 是 运行时跳转动作(通过跳转动作创建的运行时上下文具备该属性) module RuntimeModule 否 运行时模块 model RuntimeModel 否 运行时模型 view RuntimeView 否 运行时视图 viewLayout DslDefinition \| undefined 否 视图布局 DSL,从运行时视图解析获得 viewDsl DslDefinition \| undefined 否 视图模板 DSL,从运行时视图解析获得 viewTemplate DslDefinition 否 视图最终执行 DSL,从运行时视图解析获得或根据布局 DSL 和模板 DSL 合并生成 getModel (model: string, isBelong?: boolean) => GetModelResult \| undefined 否 获取模型,返回获取的模型和所在的运行时上下文 getModelField (data: string, isBelong?: boolean) => GetModelFieldResult \| undefined 否 获取模型字段,返回获取的模型字段和所在的运行时上下文 getRequestModelFields (options?: GetRequestModelFieldsOptions) => RequestModelField[] 否 获取请求字段 getDefaultValue () => Promise<Record<string, unknown>> 否 获取默认值 getInitialValue () => Promise<Record<string, unknown>> 否 获取初始值 运行时模型(model) 属性名 类型 可选性 描述 id string 是 模型 id model string 否 模型编码 name string 否 技术名称 modelFields RuntimeModelField[] 否 模型字段 modelActions RuntimeAction[] 否 模型动作 type ModelType 是 模型类型 module string 是 模块编码 moduleName string 否 模块名称 moduleDefinition RuntimeModule 是…

    2024年10月8日
    3.5K00
  • 字段组件submit方法详解

    场景介绍 在日常开发调试表单页的过程中,细心的小伙伴应该注意到,视图内的数据(通过vue调试工具看到的formData就是视图的数据)和最终通过服务端动作提交的数据不总是一致的,本文将带领大家解开疑惑。 为什么会出现这种现象? 出现这种情况都是当前模型上有关联关系字段的场景,以多对一(M2O)场景为例,由于当前模型的关联关系字段是通过字段配置中的referenceFields属性和当前模型的relationFields属性进行关联的,所以提交数据的时候只需要拿到relationFields配置的字段就可以了,没有必要再去多拿关联关系字段本身的数据。 结合业务场景说明 这里以商品模型和类目模型举例,商品模型内有个类目的m2o字段category和对应的relationFields字段categoryId,数据提交到后端的时候前端默认会根据字段配置只获取categoryId,而category的整个对象都不会被提交。 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.model.DemoItemCategory; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.common.CodeModel; @Model.model(DemoItem.MODEL_MODEL) @Model(displayName = "测试商品") public class DemoItem extends CodeModel { private static final long serialVersionUID = -5104390780952631397L; public static final String MODEL_MODEL = "demo.DemoItem"; @Field.String @Field(displayName = "商品名称") private String name; @Field.Integer @Field(displayName = "类目ID") private Long categoryId; @Field.many2one @Field.Relation(relationFields = {"categoryId"}, referenceFields = {"id"}) @Field(displayName = "商品类目") private DemoItemCategory category; } 前端是如何处理数据的 前端的字段组件提供了submit()方法来让我们可以有就会在提交数据的时候改变数据。 // 字段组件基类 export class BaseFormItemWidget< Value = unknown, Props extends BaseFormItemWidgetProps = BaseFormItemWidgetProps > extends BaseDataWidget<Props> { /** * 数据提交的方法,例如:m2o字段user(假设其关系字段为userId)的值{id: 1, name: 'xxx'},但是实际后端数据只需要其中的id,所以用m2o对应的关系字段userId提交数据就可以了 * @param submitValue */ public submit(submitValue: SubmitValue): ReturnPromise<Record<string, unknown> | SubmitRelationValue | undefined> { return undefined; } } 这里先以FormStringFieldSingleWidget组件处理密码类型的字段讲解。密码一般在输入的时候是明文,为了提高提交到后端的安全性,可以将这个密码加密后再传到后端,后端再做进一步处理,这个场景中,视图中的密码和提交给后端的密码就出现了不一致的情况, @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: [ViewType.Form, ViewType.Search], ttype: ModelFieldType.String }) ) export class FormStringFieldSingleWidget extends FormStringFieldWidget { public submit(submitValue: SubmitValue) { let finalValue = this.value; /** * 数据提交的时候,如果判断当前字段是否需要加密,需要加密的情况用encrypt函数做加密处理 */ if (this.crypto && finalValue) { finalValue = encrypt(finalValue); } return SubmitHandler.DEFAULT(this.field, this.itemName, submitValue, finalValue); } 注意:关系字段配置的透出字段只影响该字段的查询数据方法的返回值,不会因为此配置就在提交数据里加上这部分配置的字段 字段需要提交关联关系字段内的所有数据如何处理? 我们可以在自定义组件里覆写submit()方法,直接将this.value内的数据返回这里以覆写多对多m2m字段为例 import { BaseFieldWidget, FormM2MFieldSelectWidget, ModelFieldType, SPI, SubmitValue, ViewType } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.ManyToMany, widget: 'Select', model: 'xxx.yyyyy', name: 'fileName01',…

    2024年9月10日
    2.1K00
  • 如何在多标签页切换时自动刷新视图

    在日常项目中,常常会遇到多视图(Multi-View)标签的场景,用户在切换不同视图时,可能需要刷新当前活动标签内的视图数据或状态。本文将详细解析下面这段代码,并说明如何利用它在视图切换时刷新对应的视图。 下列代码写在ss-boot里面的main.ts import { VueOioProvider } from '@kunlun/dependencies'; import { delay } from 'lodash-es'; VueOioProvider( { … 自己的配置 }, [ () => { setTimeout(() => { subscribeRoute( (route) => { const page = route.segmentParams.page || {}; // 如果不是表格类型,则不刷新(根据自己的需求判断) if (page.viewType !== ViewType.Table) { return; } const { model, action } = page; const multiTabsManager = MultiTabsManager.INSTANCE; delay(() => { const tab = multiTabsManager.getActiveTab(); if (tab?.key && tab.stack.some((s) => s.parameters?.model === model && s.parameters?.action === action)) { multiTabsManager.refresh(tab.key); } }, 200); }, { distinct: true } ); }, 1000); } ] ); 1. VueOioProvider 及其作用 首先,代码通过 VueOioProvider 初始化应用程序或组件,并传入两部分参数: 配置对象:可以根据实际业务需求进行自定义配置; 回调函数数组:这里传入了一个匿名函数,用于在应用初始化后执行额外的逻辑 2. 延时执行与路由监听 在回调函数中,使用了 setTimeout 延时 1000 毫秒执行,目的通常是为了确保其他组件或全局状态已经初始化完毕,再开始进行路由监听。 随后,代码调用 subscribeRoute 来监听路由的变化。subscribeRoute 接收两个参数: 回调函数:每次路由变化时都会触发该函数,并将最新的 route 对象传递给它; 配置对象:此处使用 { distinct: true } 来避免重复的触发,提高性能。 3. 判断视图类型 在路由回调函数内部,首先通过 route.segmentParams.page 获取当前页面的配置信息。通过判断 page.viewType 是否等于 ViewType.Table,代码可以确定当前视图是否为“表格类型”: 如果不是表格类型:则直接返回,不做刷新操作; 如果是表格类型:则继续执行后续刷新逻辑。 这种判断机制保证了只有特定类型的视图(例如表格)在切换时才会触发刷新,避免了不必要的操作 4. 多视图标签的刷新逻辑 当确认当前视图为表格类型后,从 MultiTabsManager 中获取当前活动标签: MultiTabsManager.INSTANCE.getActiveTab() 返回当前活动的标签对象; 如果 key 存在,并且激活的标签内部存储的action跟url一致, 就调用 multiTabsManager.refresh(key) 方法来刷新当前标签内的视图。

    2025年3月13日
    1.5K00

Leave a Reply

Please Login to Comment