【界面设计器】自定义字段组件实战——轮播图

阅读之前

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

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

业务背景

用户需要从【创建/编辑】页面中上传多张图片,并且在【详情】页面将这多张图片进行【轮播】展示。

业务分析

从需求来看,我们需要实现一个【轮播图】组件,并且该组件允许在【详情】视图中使用。在其他视图中,我们可以直接使用平台内置的【图片】组件,实现基础的编辑和展示功能。

名词解释

  • 业务模型:需要进行可视化管理的存储模型或代理模型。

准备工作

你需要在某个业务模型下创建一个【表格视图】用于查看全部数据,创建【表单视图】用于创建/编辑数据,并创建【详情视图】展示必要的信息。(为了方便起见,你可以在所有视图中仅使用编码和名称两个字段)

你需要将【表格视图】绑定到某个菜单上,并通过【跳转动作】将三个视图进行关联,可以完整执行当前模型的全部【增删改查】操作。

业务模型定义

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

DemoModel
名称 API名称 业务类型 是否多值 长度(单值长度)
编码 code 文本 128
名称 name 文本 128
轮播图 carouselImages 文本 512

实现页面效果展示

表格视图

image.png

表单视图-创建

image.png

表单视图-编辑

image.png

详情视图

image.png

根据业务背景添加轮播图字段到所有视图

轮播图字段信息:

  • 字段业务类型:文本
  • 多值:是

使用组件:图片

无代码模型

在模型设计器创建轮播图字段,并从【组件库】-【模型】拖放至视图中即可。

PS:这里需要注意的是,在模型设计器中需要切换至专家模式,并确认字段长度为512,否则当URL超长时将无法保存。

低代码模型

与服务端同学确认字段,并从【组件库】-【模型】中拖放至视图中即可。

将上图中的【演示】数据进行【编辑】,并上传三张图片,在【详情视图】查看默认展示效果。

演示图片下载

image.png

创建组件、元件

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

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

创建轮播图组件

image.png

创建轮播图元件

根据业务背景,我们需要根据模型中的字段确定业务类型,在这个场景中,可以使用如下配置。(暂时可以不进行属性面板的设计)

image.png

在【详情视图】中将【轮播图字段】的组件切换为我们新创建的【轮播图组件】

image.png

PS:这里会发现组件变成了【输入框】的样式,这是由于我们没有提供对应元件的代码实现,使得SPI找到了默认组件。

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

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

Carousel.vue
<template>
  <a-carousel class="carousel" effect="fade" autoplay>
    <div class="carousel-item" v-for="image in images" :key="image">
      <img :src="image" :alt="image" />
    </div>
  </a-carousel>
</template>
<script lang="ts">
import { Carousel as ACarousel } from 'ant-design-vue';
import { computed, defineComponent, PropType } from 'vue';

export default defineComponent({
  name: 'Carousel',
  components: {
    ACarousel
  },
  props: {
    value: {
      type: Array as PropType<string[]>
    }
  },
  setup(props) {
    const images = computed(() => props.value || []);

    return {
      images
    };
  }
});
</script>
<style lang="scss">
.carousel {
  .slick-slide {
    height: 160px;

    & > div,
    .carousel-item {
      width: 100%;
      height: 100%;
    }

    img {
      max-width: 100%;
      max-height: 100%;
      margin: auto;
    }
  }
}
</style>
效果展示

开发完成后,我们将重新打包生成的JS文件和CSS文件在【界面设计器】的【低无一体】进行上传,就可以在【设计器环境】中正常使用了。

image.png

设计轮播图的属性面板

通过我们使用的a-carousel组件,我们发现组件中提供了很多【属性】或【功能】可以进行配置,比如是否自动切换(autoplay)、面板指示点位置(dotPosition)、是否显示面板指示点(dots)等。在这里我们将对这三个属性的配置方式进行演示,其他更多属性可以自行设计并开发。

我们可以在【界面设计器】的【属性面板设计】中根据这三个属性的字段类型确定以下信息:

功能 API名称 业务类型 选用组件 可选项
是否自动切换 autoplay 布尔 开关 -
是否显示面板指示点 dots 布尔 开关 -
面板指示点位置 dotPosition 数据字典 下拉单选 上方(top)、下方(bottom)、左侧(left)、右侧(right)

确定了这些信息后,我们在【属性面板设计】中拖入对应组件,并创建【指定API名称】的字段。

PS:数据字典类型的字段需要先在模型设计器中创建对应的数据字典,才能创建该字段。由于设计器本身的依赖关系,建议将数据字典创建在【资源】模块中,这样才可以在设计器被选中。

数据字典

image.png

image.png

PS:前端获取的值为【API名称】,并非【字典项值】。

属性面板设计

image.png

在SDK中为组件新增的属性补充代码实现

typing.ts
export enum CarouselPosition {
  top = 'top',
  bottom = 'bottom',
  left = 'left',
  right = 'right'
}
DetailStringMultiCarouselFieldWidget.ts
import { BooleanHelper, FormFieldWidget, ModelFieldType, SPI, ViewType, Widget } from '@kunlun/dependencies';
import Carousel from './Carousel.vue';
import { CarouselPosition } from './typing';

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: ViewType.Detail,
    ttype: ModelFieldType.String,
    widget: 'Carousel',
    multi: true
  })
)
export class DetailStringMultiCarouselFieldWidget extends FormFieldWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(Carousel);
    return this;
  }

  @Widget.Reactive()
  protected get autoplay() {
    return BooleanHelper.toBoolean(this.getDsl().autoplay);
  }

  @Widget.Reactive()
  protected get dots() {
    return BooleanHelper.toBoolean(this.getDsl().dots);
  }

  @Widget.Reactive()
  protected get dotPosition(): CarouselPosition | undefined {
    return this.getDsl().dotPosition;
  }
}
Carousel.vue
<template>
  <a-carousel class="carousel" effect="fade" :autoplay="autoplay" :dots="dots" :dotPosition="dotPosition">
    <div class="carousel-item" v-for="image in images" :key="image">
      <img :src="image" :alt="image" />
    </div>
  </a-carousel>
</template>
<script lang="ts">
import { Carousel as ACarousel } from 'ant-design-vue';
import { computed, defineComponent, PropType } from 'vue';
import { CarouselPosition } from './typing';

export default defineComponent({
  name: 'Carousel',
  components: {
    ACarousel
  },
  props: {
    value: {
      type: Array as PropType<string[]>
    },
    autoplay: {
      type: Boolean
    },
    dots: {
      type: Boolean
    },
    dotPosition: {
      type: String as PropType<CarouselPosition>
    }
  },
  setup(props) {
    const images = computed(() => props.value || []);

    const dotPosition = computed(() => props.dotPosition?.toLowerCase());

    return {
      images,
      dotPosition
    };
  }
});
</script>
<style lang="scss">
.carousel.ant-carousel {
  .slick-slide,
  .slick-vertical {
    height: 160px;
  }

  .slick-slide {
    & > div,
    .carousel-item {
      width: 100%;
      height: 100%;
    }

    img {
      max-width: 100%;
      max-height: 100%;
      margin: auto;
    }
  }
}
</style>

开发完成后,我们将重新打包生成的JS文件和CSS文件在【界面设计器】的【低无一体】进行上传,就可以在【设计器环境】中正常使用了。

设计组件优化

在执行到以上步骤之后,我们发现执行页面和设计页面的属性面板都可以正常运行,美中不足的是,我们无法在设计器直观看到预览效果。为了解决这一问题,我们需要对设计组件进行相关的优化。

细心的同学可能也会发现,我们在组件中对高度的设定是160px,这个设置会导致用户无法根据需求进行定制。不仅如此,由于宽度属性未进行配置,用户也无法根据需求调整宽度。

为了解决上述问题,我们可以将属性面板稍作调整。

宽度使用内置的数据字典类型的宽度即可,高度需要我们新建一个高度(height)的整数字段,并添加后缀提示用户高度的单位。

效果如下图所示:

image.png

接下来,我们仅需实现【预览效果】和【高度】属性即可,内置的【宽度】属性是通过外部控制的,组件本身无需关心。

实现思路:

  • 由于设计器的预览功能移除了Class Component(ts)组件的相关功能,而是直接将属性面板的值传递到Vue组件中,因此,我们需要在Vue组件中判断当前组件是否在设计器的预览环境中,并且根据这个判断提供相应的预览效果。我们可以使用代码示例中的isDesignComponent属性,来实现这一功能。
  • 高度在用户输入时为【整数】,因此是没有单位的,可以使用平台内置的StyleHelper#px方法进行转换。
Carousel.vue
<template>
  <a-carousel class="carousel" effect="fade" :autoplay="autoplay" :dots="dots" :dotPosition="dotPosition">
    <template v-if="isDesignComponent">
      <div class="carousel-item carousel-item-demo"><h3>1</h3></div>
      <div class="carousel-item carousel-item-demo"><h3>2</h3></div>
      <div class="carousel-item carousel-item-demo"><h3>3</h3></div>
    </template>
    <template v-else>
      <div class="carousel-item" v-for="image in images" :key="image">
        <img :src="image" :alt="image" />
      </div>
    </template>
  </a-carousel>
</template>
<script lang="ts">
import { StyleHelper } from '@kunlun/dependencies';
import { Carousel as ACarousel } from 'ant-design-vue';
import { computed, defineComponent, PropType } from 'vue';
import { CarouselPosition } from './typing';

export default defineComponent({
  name: 'Carousel',
  components: {
    ACarousel
  },
  props: {
    value: {
      type: Array as PropType<string[]>
    },
    autoplay: {
      type: Boolean
    },
    dots: {
      type: Boolean
    },
    dotPosition: {
      type: String as PropType<CarouselPosition>
    },
    height: {
      type: [Number, String]
    },
    isDesignComponent: {
      type: Boolean,
      default: true
    }
  },
  setup(props) {
    const images = computed(() => props.value || []);

    const dotPosition = computed(() => props.dotPosition?.toLowerCase());

    const height = computed(() => StyleHelper.px(props.height) || '160px');

    return {
      images,
      dotPosition,
      height
    };
  }
});
</script>
<style lang="scss">
.carousel.ant-carousel {
  .slick-slide,
  .slick-vertical {
    height: v-bind(height);
  }

  .slick-slide {
    & > div,
    .carousel-item {
      width: 100%;
      height: 100%;
    }

    img {
      max-width: 100%;
      max-height: 100%;
      margin: auto;
    }
  }

  .carousel-item-demo {
    display: flex !important;
    align-items: center;
    justify-content: center;
    background-color: #364d79;

    h3 {
      color: #ffffff;
    }
  }
}
</style>
DetailStringMultiCarouselFieldWidget.ts
import { BooleanHelper, FormFieldWidget, ModelFieldType, SPI, ViewType, Widget } from '@kunlun/dependencies';
import Carousel from './Carousel.vue';
import { CarouselPosition } from './typing';

@SPI.ClassFactory(
  FormFieldWidget.Token({
    viewType: ViewType.Detail,
    ttype: ModelFieldType.String,
    widget: 'Carousel',
    multi: true
  })
)
export class DetailStringMultiCarouselFieldWidget extends FormFieldWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(Carousel);
    return this;
  }

  @Widget.Reactive()
  protected get autoplay() {
    return BooleanHelper.toBoolean(this.getDsl().autoplay);
  }

  @Widget.Reactive()
  protected get dots() {
    return BooleanHelper.toBoolean(this.getDsl().dots);
  }

  @Widget.Reactive()
  protected get dotPosition(): CarouselPosition | undefined {
    return this.getDsl().dotPosition;
  }

  @Widget.Reactive()
  protected get height(): number | string | undefined {
    return this.getDsl().height;
  }

  @Widget.Reactive()
  protected get isDesignComponent() {
    return false;
  }
}

开发完成后,我们将重新打包生成的JS文件和CSS文件在【界面设计器】的【低无一体】进行上传,就可以在【设计器环境】中正常使用了。

除了上述我们演示的功能实现外,其实轮播图组件还可以有其他更多的功能。比如:轮播图中的图片展示方式可以是拉伸、平铺、等比缩放等,轮播图分页样式,轮播图是否展示左右翻页按钮以及翻页按钮的样式,轮播动画效果等等。

结语

至此,我们已经基本完成了一个简单的轮播图组件。

从轮播图组件的实现来看,对我们传统开发思维提出了一些挑战。

在传统组件开发中,我们开发的一个一个Vue组件都是提供给其他开发人员使用的,甚至绝大多数组件是由于单个组件的复杂性进行拆分的一个一个小组件。

在传统开发思维中,我们很难感受到当一个组件具备用户输入能力时,它所展示的最终效果。甚至在某些场景中,用户输入的结果并非是我们所能预知的。

对于低代码组件而言,其使得用户对组件可以有一定程度的输入能力。正是由于存在这样的差异,我们更需要站在用户角度去思考组件该通过怎样的输入得到怎样的输出

从结果上来看,低代码组件为用户提供的输入能力可以使得组件应用的场景更加广泛,再结合界面设计器的在线设计能力,可以促使我们在开发组件时使其具有更高的复用能力。

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

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

Like (0)
数式-海波's avatar数式-海波数式管理员
Previous 2023年6月20日 pm4:07
Next 2023年11月2日 pm1:58

相关推荐

  • 前端元数据介绍

    模型 属性名 类型 描述 id string 模型id model string 模型编码 name string 技术名称 modelFields RuntimeModelField[] 模型字段 modelActions RuntimeAction[] 模型动作 type ModelType 模型类型 module string 模块编码 moduleName string 模块名称 moduleDefinition RuntimeModule 模块定义 pks string[] 主键 uniques string[][] 唯一键 indexes string[][] 索引 sorting string 排序 label string 显示标题 labelFields string[] 标题字段 模型字段 属性名 类型 描述 model string 模型编码 modelName string 模型名称 data string 属性名称 name string API名称 ttype ModelFieldType 字段业务类型 multi boolean (可选) 是否多值 store boolean 是否存储 displayName string (可选) 字段显示名称 label string (可选) 字段页面显示名称(优先于displayName) required boolean | string (可选) 必填规则 readonly boolean | string (可选) 只读规则 invisible boolean | string (可选) 隐藏规则 disabled boolean | string (可选) 禁用规则 字段业务类型 字段类型 值 描述 String ‘STRING’ 文本 Text ‘TEXT’ 多行文本 HTML ‘HTML’ 富文本 Phone ‘PHONE’ 手机 Email ‘EMAIL’ 邮箱 Integer ‘INTEGER’ 整数 Long ‘LONG’ 长整型 Float ‘FLOAT’ 浮点数 Currency ‘MONEY’ 金额 DateTime ‘DATETIME’ 时间日期 Date ‘DATE’ 日期 Time ‘TIME’ 时间 Year ‘YEAR’ 年份 Boolean ‘BOOLEAN’ 布尔型 Enum ‘ENUM’ 数据字典 Map ‘MAP’ 键值对 Related ‘RELATED’ 引用类型 OneToOne ‘O2O’ 一对一 OneToMany ‘O2M’ 一对多 ManyToOne ‘M2O’ 多对一 ManyToMany ‘M2M’ 多对多 模型动作 属性名 类型 描述 name string…

    2024年9月21日
    1.9K00
  • 「前端」 获取当前用户信息以及语言

    概述 TopBarService 是用于管理用户信息和多语言配置的工具类,提供以下核心功能: 用户信息查询(含缓存机制) 当前语言状态获取 可用语言列表查询 语言切换操作 快速开始 // 基础使用示例 import { TopBarService } from '@kunlun/dependencies'; // 获取用户信息(自动缓存) const userInfo = await TopBarService.getUserInfo(); // 获取当前语言配置 const currentLang = await TopBarService.getCurrentLang(); // 查询支持的语言列表 const languages = await TopBarService.queryLanguageList(); // 激活指定语言 await TopBarService.activeLang('zh_CN'); API 详解 方法功能说明 方法名称 描述 参数 返回值 ​getUserInfo 获取当前用户信息(自动缓存) 无 用户信息对象 ​queryUserInfo 强制获取最新用户信息 无 最新用户信息对象 ​getCurrentLang 获取当前激活的语言配置 无 语言配置对象 / undefined ​queryLanguageList 获取所有可用语言列表 无 语言列表数组 ​activeLang 激活指定语言 语言 ID (string) 更新后的语言配置对象

    2025年3月21日
    1.8K00
  • 自定义视图组件(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.7K00
  • 列表视图、卡片视图切换

    在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式. 这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格跟卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。 我们用资源模块下面的国家菜单来实现这么一个功能这是对应的URL: http://localhost:8080/page;module=resource;viewType=TABLE;model=resource.ResourceCountry;action=resource%23%E5%9B%BD%E5%AE%B6;scene=resource%23%E5%9B%BD%E5%AE%B6;target=OPEN_WINDOW;menu=%7B%22selectedKeys%22:%5B%22%E5%9B%BD%E5%AE%B6%22%5D,%22openKeys%22:%5B%22%E5%9C%B0%E5%9D%80%E5%BA%93%22,%22%E5%9C%B0%E5%8C%BA%22%5D%7D 源码下载 views 创建外层的视图容器 刚刚我们讲过,不管是表格还是卡片,它都在当前的视图里面,所以我们需要写一个视图容器来包裹它们,并且对应的容器里面允许拆入表格跟卡片,我们先创建TableWithCardViewWidget.ts // TableWithCardViewWidget.ts import { BaseElementWidget, SPI, Widget } from '@kunlun/dependencies'; import TableWithCardView from './TableWithCardView.vue'; enum ListViewType { TABLE = 'table', CARD = 'card' } @SPI.ClassFactory( BaseElementWidget.Token({ widget: 'TableWithCardViewWidget' }) ) export class TableWithCardViewWidget extends BaseElementWidget { @Widget.Reactive() private listViewType: ListViewType = ListViewType.TABLE; // 当前视图展示的类型,是展示卡片还是表格 public initialize(props) { if (!props.slotNames) { props.slotNames = ['tableWidget', 'cardWidget']; } super.initialize(props); this.setComponent(TableWithCardView); return this; } } 在TableWithCardViewWidget中的initialize函数中,我们定义了两个插槽: tableWidget、cardWidget,所以需要在对应的vue文件里面里接收这两个插槽 <template> <div class="list-view-wrapper"> <!– 表格插槽 –> <div style="height: 100%" v-if="listViewType === 'table'"> <slot name="tableWidget" /> </div> <!– 卡片插槽 –> <div v-if="listViewType === 'card'"> <slot name="cardWidget"></slot> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: ['listViewType', 'onChangeViewType'], inheritAttrs: false, setup(props, context) { const onChangeViewType = (listViewType) => { props.onChangeViewType(listViewType); }; } }); </script> 这样一来,我们就定义好了视图容器,接下来就是通过自定义layout的方式注册该容器 layout注册 import { registerLayout, ViewType } from '@kunlun/dependencies'; registerLayout( `<view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" cols="4" slot="search"/> </view> </pack> <pack widget="group" slot="tableGroup" style="position: relative"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="TableWithCardViewWidget"> <template slot="tableWidget"> <element widget="table" slot="table" datasource-provider="true"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" />…

    2024年10月16日
    2.7K01
  • 【界面设计器】自定义字段组件实战——表格字段组合展示

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 表格中的一列使用多个字段组合展示。 演示内容:表格中存在两列,【编码】和【基础信息】。将【名称】、【创建时间】、【更新时间】在【基础信息】一列展示。 业务分析及实现思路 从需求来看,我们需要实现一个【组合列】组件,并且该组件允许在【表格】视图中使用。由于【组合列】本身也是一个字段,因此这里需要选择需要组合字段中的其中一个字段作为组件切换的基础字段,比如我们可以选择【名称】字段作为基础字段。 在【组合列】组件的属性面板中,我们需要再自定义一个【组合列配置】组件,用来选择需要将哪些字段进行组合,以及为每个组合提供一些基础配置。 这里需要理解一个基本概念,即【组合列】的属性面板是【组合列配置】的【执行页面】。所有组件的属性面板在【执行页面】时都是【表单】视图。 因此我们可以实现一个【组合列配置】组件,并且该组件允许在【表单】视图中使用。其业务类型使用【文本】,我们在保存配置数据时,可以使用JSON数据结构来存储复杂结构。(这里的实现思路并非是最符合协议设定的,但可以满足绝大多数组件场景) 在【组合列配置】组件中,我们可以允许用户添加/移除组合,并且每个组合有两个属性,【标题】和【字段】。 准备工作 此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 创建时间 createDate 日期时间 否 – 更新时间 updateDate 日期时间 否 – 实现页面效果展示 表格视图 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建组合列组件 创建组合列元件 创建组合列配置组件 创建组合列配置元件 设计组合列元件属性面板 创建compositeConfig字段,并切换至【组合配置】组件。 设计组合列配置元件属性面板 启动SDK工程进行组件基本功能开发 PS:这里由于我们创建了两个组件,因此,将SDK分开下载后,然后将组合列配置SDK中的演示代码(kunlun-plugin/src)移动到组合列SDK中,在同一工程中进行开发,最后只需将相关JS文件和CSS文件上传到组合列组件中即可,组合列配置组件可以不进行上传。这里需要注意的是,上传多个包含相同组件功能的JS文件和CSS文件可能在运行时导致无法正常替换、冲突等问题。 (npm相关操作请自行查看SDK工程中内置的README.MD) 开发步骤参考 打开【表格】视图,将【名称】字段的组件切换为【组合列】组件。 在属性面板中看到【组合列配置】组件,并优先实现【组合列配置】组件。这里的属性面板就是【组合列配置】对应的【执行页面】。 当【组合列配置】组件可以按照预先设计的数据结构正确保存compositeConfig属性时,可以在【组合列】组件中的props定义中直接获取该属性,接下来就可以进行【组合列】组件的开发。 代码实现参考 工程结构 typing.ts export interface CompositeConfig { key: string; label?: string; field?: string; value?: string; } CompositeColumnConfig.vue <template> <div class="composite-column-config"> <oio-form v-for="item in list" :data="item" :key="item.key"> <oio-form-item label="标题" name="label"> <oio-input v-model:value="item.label" /> </oio-form-item> <oio-form-item label="字段" name="field"> <a-select class="oio-select" dropdownClassName="oio-select-dropdown" v-model:value="item.field" :options="fields" /> </oio-form-item> <oio-button type="link" @click="() => removeItem(item)">移除</oio-button> </oio-form> <oio-button type="primary" block @click="addItem">添加</oio-button> </div> </template> <script lang="ts"> import { uniqueKeyGenerator } from '@kunlun/dependencies'; import { WidgetInstance } from '@kunlun/ui-designer-dependencies'; import { OioButton, OioForm, OioFormItem, OioInput } from '@kunlun/vue-ui-antd'; import { Select as ASelect } from 'ant-design-vue'; import { computed, defineComponent, PropType, ref, watch } from 'vue'; import { CompositeConfig } from '../../typing'; export default defineComponent({ name: 'CompositeColumnConfig', components: { OioForm, OioFormItem, OioInput, OioButton, ASelect }, props: { currentInstance: { type:…

    2023年11月1日
    1.6K00

Leave a Reply

Please Login to Comment