自定义组件之手动渲染弹出层(v4)

阅读之前

你应该:

弹出层组件

我们内置了两个弹出层组件,弹窗(Dialog)抽屉(Drawer),以下所有内容全部围绕弹窗(Dialog)进行描述,抽屉相关内容与弹窗完全一致。

下面这个对照表格可以帮助你区分两个弹出层组件的异同:

弹出层相关功能 弹窗(Dialog) 抽屉(Drawer)
API方法 Dialog#create Drawer#create
内置组件 DialogWidget DrawerWidget
内置插槽 header, default, footer header, default, footer

渲染弹出层的方式

我们提供了三种渲染弹出层组件的方式,根据不同的情况使用不同的方式可以让实现变得更简单。

  • 使用Dialog#createAPI方法创建弹窗:一般用于简单场景,动作区无法进行自定义。
  • 使用DSL渲染能力创建弹窗
    • 调用ActionWidget#click方法打开弹窗(适用于自动渲染场景)
    • 使用createWidget创建DialogWidget并打开弹窗(适用于手动渲染场景)
  • 使用第三方组件创建弹窗:一般用于弹窗需要自定义的场景中,性能最优,可用于任何场景。

使用Dialog#createAPI方法创建弹窗

以下是一个自定义组件的完整示例,其使用ViewCache#compule方法获取视图。

view.ts
export const template = `<view>
    <field data="id" invisible="true" />
    <field data="code" label="编码" />
    <field data="name" label="名称" />
</view>`;
ManualDemoWidget.ts
import {
  BaseElementWidget,
  createRuntimeContextForWidget,
  Dialog,
  FormWidget,
  MessageHub,
  RuntimeView,
  SPI,
  ViewCache,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import ManualDemo from './ManualDemo.vue';
import { template } from './view';

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' }))
export class ManualDemoWidget extends BaseElementWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(ManualDemo);
    return this;
  }

  @Widget.Method()
  public async openPopup() {
    // 获取运行时视图
    const view = await this.fetchViewByCompile();
    if (!view) {
      console.error('Invalid view');
      return;
    }

    // 创建运行时上下文
    const runtimeContext = createRuntimeContextForWidget(view);
    const runtimeContextHandle = runtimeContext.handle;

    // 获取初始化数据
    const formData = await runtimeContext.getInitialValue();

    // 创建弹窗
    const dialogWidget = Dialog.create();

    // 设置弹窗属性
    dialogWidget.setTitle('这是一个演示弹窗');

    // 创建所需组件
    dialogWidget.createWidget(FormWidget, undefined, {
      metadataHandle: runtimeContextHandle,
      rootHandle: runtimeContextHandle,
      dataSource: formData,
      activeRecords: formData,
      template: runtimeContext.viewTemplate,
      inline: true
    });

    dialogWidget.on('ok', () => {
      MessageHub.info('click ok');

      // 关闭弹窗
      dialogWidget.onVisibleChange(false);
    });

    dialogWidget.on('cancel', () => {
      MessageHub.info('click cancel');

      // 关闭弹窗
      dialogWidget.onVisibleChange(false);
    });

    // 打开弹窗
    dialogWidget.onVisibleChange(true);
  }

  public async fetchViewByCompile(): Promise<RuntimeView | undefined> {
    // 模型编码
    const model = ${model};

    // 通过编译获取视图(非完整视图数据)
    const view = await ViewCache.compile(model, '$$popup_demo', template);
    if (!view) {
      return;
    }
    // 补充视图类型
    view.type = ViewType.Form;

    return view;
  }
}
ManualDemo.vue
<template>
  <div class="manual-demo">
    <oio-button @click="openPopup">打开弹窗</oio-button>
  </div>
</template>
<script lang="ts">
import { OioButton } from '@kunlun/vue-ui-antd';
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ManualDemo',
  components: {
    OioButton
  },
  props: {
    openPopup: {
      type: Function
    }
  }
});
</script>
视图DSL
<view type="FORM">
    <template slot="form">
        <field data="id" invisible="true" />
        <field data="code" />
        <field data="name" />
        <element widget="ManualDemo" />
    </template>
</view>

关键点详解

......

使用DSL渲染能力创建弹窗(调用ActionWidget#click方法)

以下是一个自定义组件的完整示例,视图被定义在DSL模板中。

ManualDemoWidget.ts
import { ActionWidget, BaseElementWidget, DslDefinitionWidget, SPI, Widget } from '@kunlun/dependencies';
import ManualDemo from './ManualDemo.vue';

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' }))
export class ManualDemoWidget extends BaseElementWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(ManualDemo);
    return this;
  }

  @Widget.Method()
  public async openPopup() {
    const actionWidget = this.getChildren().find((v) => {
      if (v instanceof DslDefinitionWidget) {
        const slotName = v.getSlotName();
        if (slotName === 'popupAction' && v instanceof ActionWidget) {
          return v;
        }
      }
      return false;
    }) as ActionWidget;

    if (actionWidget) {
      await actionWidget.click();
      actionWidget.forceUpdate();
    }
  }
}
ManualDemo.vue
<template>
  <div class="manual-demo">
    <oio-button @click="openPopup">打开弹窗</oio-button>
    <div style="display: none">
      <slot name="popupAction" />
    </div>
  </div>
</template>
<script lang="ts">
import { OioButton } from '@kunlun/vue-ui-antd';
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ManualDemo',
  components: {
    OioButton
  },
  props: {
    openPopup: {
      type: Function
    }
  }
});
</script>
视图DSL
<view type="FORM">
    <template slot="form">
        <field data="id" invisible="true" />
        <field data="code" />
        <field data="name" />
        <element widget="ManualDemo">
            <template slot="popupAction">
                <action name="$$popup_action_demo" actionType="VIEW" contextType="CONTEXT_FREE" target="DIALOG" resModel="${model}">
                    <template slot="default" title="这是一个演示弹窗">
                        <view type="FORM">
                            <template slot="actionBar">
                                <action name="$$internal_DialogCancel" type="default" />
                                <action name="create" />
                            </template>
                            <template slot="form">
                                <field data="id" invisible="true" />
                                <field data="code" />
                                <field data="name" />
                            </template>
                        </view>
                    </template>
                </action>
            </template>
        </element>
    </template>
</view>

关键点详解

......

使用DSL渲染能力创建弹窗(创建DialogWidget组件)

以下是一个自定义组件的完整示例,其使用ViewCache#compule方法获取视图,该视图对应的是弹窗组件的渲染模板。

view.ts
export const template = `<view title="这是一个演示弹窗">
    <template slot="default">
        <view type="FORM">
            <element widget="form">
                <field data="id" invisible="true" />
                <field data="code" />
                <field data="name" />
            </element>
        </view>
    </template>
    <template slot="footer">
        <element widget="actionBar">
            <action name="$$internal_DialogCancel" type="default" />
            <action name="create" />
        </element>
    </template>
</view>`;
ManualDemoWidget.ts
import {
  BaseElementWidget,
  createRuntimeContextForWidget,
  DialogWidget,
  RuntimeView,
  SPI,
  ViewCache,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import ManualDemo from './ManualDemo.vue';
import { template } from './view';

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' }))
export class ManualDemoWidget extends BaseElementWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(ManualDemo);
    return this;
  }

  @Widget.Method()
  public async openPopup() {
    // 获取运行时视图
    const view = await this.fetchViewByCompile();
    if (!view) {
      console.error('Invalid view');
      return;
    }

    // 创建运行时上下文
    const runtimeContext = createRuntimeContextForWidget(view);
    const runtimeContextHandle = runtimeContext.handle;

    // 获取初始化数据
    const formData = await runtimeContext.getInitialValue();

    const dialogWidget = this.createWidget(new DialogWidget(), 'popupWidget', {
      metadataHandle: runtimeContextHandle,
      rootHandle: runtimeContextHandle,
      dataSource: formData,
      activeRecords: formData,
      template: runtimeContext.viewTemplate,
      inline: true,
      mountedVisible: true
    });

    dialogWidget.on('ok', async () => {
      dialogWidget.onVisibleChange(false);
    });
    dialogWidget.on('cancel', () => {
      dialogWidget.onVisibleChange(false);
    });

    this.forceUpdate();
  }

  public async fetchViewByCompile(): Promise<RuntimeView | undefined> {
    // 模型编码
    const model = ${model};

    // 通过编译获取视图(非完整视图数据)
    const view = await ViewCache.compile(model, '$$popup_demo', template);
    if (!view) {
      return;
    }
    // 补充视图类型
    view.type = ViewType.Form;

    return view;
  }
}
ManualDemo.vue
<template>
  <div class="manual-demo">
    <oio-button @click="openPopup">打开弹窗</oio-button>
    <div style="display: none">
      <slot name="popupWidget" />
    </div>
  </div>
</template>
<script lang="ts">
import { OioButton } from '@kunlun/vue-ui-antd';
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ManualDemo',
  components: {
    OioButton
  },
  props: {
    openPopup: {
      type: Function
    }
  }
});
</script>
视图DSL
<view type="FORM">
    <template slot="form">
        <field data="id" invisible="true" />
        <field data="code" />
        <field data="name" />
        <element widget="ManualDemo" />
    </template>
</view>

关键点详解

......

使用第三方组件创建弹窗

以下是一个自定义组件的完整示例,其使用ViewCache#compule方法获取视图。

view.ts
export const template = `<view>
    <field data="id" invisible="true" />
    <field data="code" label="编码" />
    <field data="name" label="名称" />
</view>`;
ManualDemoWidget.ts
import {
  BaseElementWidget,
  createRuntimeContextForWidget,
  FormWidget,
  MessageHub,
  RuntimeView,
  SPI,
  ViewCache,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import ManualDemo from './ManualDemo.vue';
import { template } from './view';

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ManualDemo' }))
export class ManualDemoWidget extends BaseElementWidget {
  private formWidget: FormWidget | undefined;

  public initialize(props) {
    super.initialize(props);
    this.setComponent(ManualDemo);
    return this;
  }

  @Widget.Method()
  public async openPopup(): Promise<void> {
    // 获取运行时视图
    const view = await this.fetchViewByCompile();
    if (!view) {
      console.error('Invalid view');
      return;
    }

    // 销毁旧组件
    this.formWidget?.dispose();
    this.formWidget = undefined;

    // 创建运行时上下文
    const runtimeContext = createRuntimeContextForWidget(view);
    const runtimeContextHandle = runtimeContext.handle;

    // 获取初始化数据
    const formData = await runtimeContext.getInitialValue();

    // 创建所需组件
    this.formWidget = this.createWidget(FormWidget, 'formWidget', {
      metadataHandle: runtimeContextHandle,
      rootHandle: runtimeContextHandle,
      dataSource: formData,
      activeRecords: formData,
      template: runtimeContext.viewTemplate,
      inline: true
    });
    this.forceUpdate();
  }

  @Widget.Method()
  public onEnter() {
    MessageHub.info('click ok');
    return true;
  }

  @Widget.Method()
  public onCancel() {
    MessageHub.info('click cancel');
    return true;
  }

  public async fetchViewByCompile(): Promise<RuntimeView | undefined> {
    // 模型编码
    const model = ${model};

    // 通过编译获取视图(非完整视图数据)
    const view = await ViewCache.compile(model, '$$popup_demo', template);
    if (!view) {
      return;
    }
    // 补充视图类型
    view.type = ViewType.Form;

    return view;
  }
}
ManualDemo.vue
<template>
  <div class="manual-demo">
    <oio-button @click="openModal">打开弹窗</oio-button>
    <oio-modal v-model:visible="visible" title="这是一个演示弹窗" :enter-callback="onEnter" :cancel-callback="onCancel">
      <slot name="formWidget" />
    </oio-modal>
  </div>
</template>
<script lang="ts">
import { ManualWidget } from '@kunlun/dependencies';
import { OioButton, OioModal } from '@kunlun/vue-ui-antd';
import { defineComponent, nextTick, ref } from 'vue';

export default defineComponent({
  name: 'ManualDemo',
  mixins: [ManualWidget],
  components: {
    OioButton,
    OioModal
  },
  props: {
    openPopup: {
      type: Function
    },
    onEnter: {
      type: Function
    },
    onCancel: {
      type: Function
    }
  },
  setup(props) {
    const visible = ref(false);

    const openModal = async () => {
      await props.openPopup?.();
      nextTick(() => {
        visible.value = true;
      });
    };

    return {
      visible,

      openModal
    };
  }
});
</script>
视图DSL
<view type="FORM">
    <template slot="form">
        <field data="id" invisible="true" />
        <field data="code" />
        <field data="name" />
        <element widget="ManualDemo" />
    </template>
</view>

关键点详解

......

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

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

(0)
nation的头像nation数式员工
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

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

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 用户在输入【金额】字段时,实时展示千分位格式。 业务分析 从需求来看,我们需要实现一个【千分位】组件,并且该组件允许在【表单】视图中使用。 扩展实现:该组件虽然仅要求在【表单】中使用,但也可以在【搜索】中使用完全相同的实现,因此这里我们在注册时会增加【搜索】视图,将【千分位】组件应用在【搜索】中。对于【表格】、【详情】和【画廊】来说,该组件是没有【输入】行为的展示组件,在这里我们不进行演示。 准备工作 此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 金额 money 金额 否 – 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建千分位组件 创建千分位元件 启动SDK工程进行组件基本功能开发 (npm相关操作请自行查看SDK工程中内置的README.MD) 关键点详解 数据交互类型的字段组件(以下简称展示组件)与仅展示类型的字段组件(以下简称交互组件)有一些差别。 通常情况下,在展示组件中仅需使用value属性即可展示相关内容。在交互组件中除了value用于展示外,还需使用change、focus以及blur处理用户输入时的基本交互逻辑。 数据交互方法主要功能说明: change方法:当值发生变更时调用,根据字段相关配置将值回填至表单中。 focus方法:当组件获取焦点时调用,记录当前值,并在调用blur方法时进行处理。 blur方法:当前组件失去焦点时调用,根据focus方法记录的值,进行失焦触发逻辑的执行。 这里的三个数据交互方法仅仅是对用户行为的抽象,而并非限定其使用场景。 通常来说,这三个方法的调用顺序为:focus –> change –> blur。 如在日期时间组件中,面板打开时调用了focus方法,面板值发生变更时,调用了change方法,面板关闭时调用了blur方法。 如在地图组件中,选中地图上的某个点时仅会调用change方法,用户交互上并不能体现出focus和blur的行为。因此对于这个组件而言,只会有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:…

    2024年4月19日
    76500
  • 从一个方法开始:浅析页面渲染流程

    渲染前的准备 渲染前的准备,在 Vue 渲染框架下,会先安装所有所支持的默认组件,比如 Mask,Header 等,这些组件支持 XML 默认模版的 Vue 框架下的渲染,详情可见 main.ts 中,maskInstall 与 install,这两个函数将平台内部支持的组件进行了注册,随后将整个 Vue 挂载为运行时 App,随后进行初始化。 渲染的起步 OioProvider 方法是整个应用的入口,我们忽略掉一些配置方法,将注意力集中到 initializeServices 从名称中我们可以看出来内部保存的都是初始化服务,其中提供了渲染服务等,我们当前使用的是 Vue 框架,所以当前其渲染的 Root 节点为 Vue, 以此,我们视野可以暂时转移到 admin-base中的 Root.vue以及 RootWidget上, 其实现了整个 Vue 框架下的 Root 节点如何渲染,其中定义了多个 widget,比如登陆页,首页,忘记密码已经重置密码等页面, 在本文中我们着重关注渲染首页的能力,RootWidget将 DefaultMetadataMainViewWidget作为渲染 Props中的 page即首页提供给下层组件使用, 渐入佳境 DefaultMetadataMainViewWidget从名称中可以看到,其为元数据主视图,主要做两件事,1:提供 Mask 的渲染2:提供元数据上下文初始化 该组件主要通过观察路由变化触发上面两个动作,当路由发生变化,该组件 reloadPage将被调用,reloadPage方法通过组装路由参数构成一个唯一 key 向后段查询当前路由所对应的渲染信息, 随后将获取到的信息进行处理,初始化,即 元数据上下文初始化,在初始化后,会将获取到的数据注入到 MetadataViewWidget中, Mask 的渲染 关于 Mask 的渲染,在获取到数据后,将生成 maskTemplate,并将其赋值, DefaultMetadataMainView.vue文件将渲染该模板,并渲染到页面中 数据的变更 当上面两件任务完成后,将开始主视图的渲染,上文提到,DefaultMetadataMainViewWidget只负责 mask 的渲染和上下文的初始化,所以 DefaultMetadataMainViewWidget通过触发事件的方式来实现主视图的渲染, DefaultMetadataMainViewWidget将必要信息作为事件参数触发,MultiTabsContainerWidget接收到 reloadMainViewCallChaining事件后,开启主视图渲染, MultiTabsContainerWidget会刷新运行时上下文,即 refreshRuntimeContext,该方法将尝试查询并通过 createOrUpdateEnterTab方法创建 Tab 页,createOrUpdateEnterTab最终生成一个 MultiTabItem格式的对象,该对象描述了 Tab 的相关信息,随后调用 createTabContainerWidget创建 tab 的容器即新建了一个 MultiTabContainerWidget组件即单个 tab 的容器,随后调用 setActiveTabItem, 并获取其绑定的 Vue 组件,并将其组件放置在 KeepAlive内部,触发更新, 主视图的渲染 MultiTabContainerWidget继承自MetadataViewWidget,当 MetadataViewWidget数据发生变更, 其绑定的 Vue 组件将解析 viewTemplate, 获取到与该模板 dslNodeType想匹配的 Vue 组件,当前例子中为 View.vue,随后 View.vue开始渲染,View.vue文件只是一个纯粹的容器,当 View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,View.vue需要做的就是将这个 template翻译并渲染成 DOM 展现在浏览器上, 渲染整个页面 当 View.vue被挂载时,其内部的 template属性包含了整个页面的描述信息,View.vue主要做了两个事情,一:将 template 中的 widget转换为组件,二:根据当前的 template信息生成 slot信息, const currentSlots = computed<Slots | undefined>( () => DslRender.fetchVNodeSlots(props.dslDefinition) || (Object.keys(context.slots).length ? context.slots : undefined) ); const renderWidget = createCustomWidget(InternalWidget.View, { …context.attrs, type: props.type || viewType.value, template: props.dslDefinition, metadataHandle: props.metadataHandle || metadataHandle.value, rootHandle: props.rootHandle || rootHandle.value, parentHandle: props.parentHandle || parentHandle.value, slotName: props.slotName, inline: inlineProp } as ViewWidgetProps); 生成这两部分信息后,View.vue会将这两部分挂载到页面上,这两部分从代码中可以看出,主要靠 fetchVNodeSlots,createCustomWidget两个函数, export function createCustomWidget( widget: string, props: CustomWidgetProps ): RenderWidget | undefined public static fetchVNodeSlots(dsl: DslDefinition | undefined, supportedSlotNames?: string[]):…

    2025年3月19日
    28500
  • 列表视图、卡片视图切换

    在日常项目开发中,我们可能会遇到当前视图是个表格,通过某个操作按钮将它变成卡片的形式. 这篇文章将带大家实现这种功能。通过上面两张图片可以看到出来不管是表格还是卡片,它都在当前的视图里面,所以我们需要一个视图容器来包裹表格跟卡片,并且表格可以使用平台默认的表格渲染,我们只需要自定义卡片即可。 我们用资源模块下面的国家菜单来实现这么一个功能这是对应的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日
    1.6K01
  • 表格字段配置Switch开关

    在业务开发中,我们经常会遇到表格中有个Switch开关组件: 那么如何通过界面设计器配置这个组件呢,下面让我们一起来学习下吧。 设计布尔类型的组件 1: 首先在界面设计器的组件区域添加一个组件。 2: 我们给这个组件添加一个元件,元件的配置必须跟下面的一致 3: 给元件添加属性配置 拖拽一个单行文本字段, 字段编码必须是truthyAction,代表的是该字段为true的时候要执行的动作 再拖拽一个单行文本字段, 字段编码必须是falsyAction,代表的是该字段为false的时候要执行的动作 4: 发布元件,然后再去对应的界面设计器页面,将对应的字段切换成刚刚设计的组件 5: 发布界面设计器,染红我们可以在运行时的页面看到效果 当switch切换的时候,会执行对应的action

    2023年11月21日
    1.2K00
  • 组件SPI机制(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 组件SPI简介 不论是母版、布局还是DSL,所有定义在模板中的标签都是通过组件SPI机制获取到对应Class Component(ts)并继续执行渲染逻辑。 基本概念: 标签:xml中的标签,json中的dslNodeType属性。 Token组件:用于收集一组Class Component(ts)的基础组件。通常该基础组件包含了对应的一组基础能力(属性、函数等) 维度(dsl属性):用于从Token组件收集的所有Class Component(ts)组件中查找最佳匹配的参数。 组件SPI机制将通过指定维度按照有权重的最长路径匹配算法获取最佳匹配的组件。 组件注册到指定Token组件 以BaseFieldWidget这个SPIToken组件为例,可以用如下方式,注册一个可以被field标签处理的自定义组件: (以下示例仅仅为了体现SPI注册的维度,而并非实际业务中使用的组件代码) 注册一个String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String }) ) export class FormStringFieldWidget extends BaseFieldWidget { …… } 注册一个多值的String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 是否多值:是 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且该字段为多值字段 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, multi: true }) ) export class FormStringMultiFieldWidget extends BaseFieldWidget { …… } 注册一个String类型的Hyperlinks组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 组件名称:Hyperlinks 说明: 该字段组件仅可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且组件名称必须指定为Hyperlinks @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, widget: 'Hyperlinks' }) ) export class FormStringHyperlinksFieldWidget extends BaseFieldWidget { …… } 当上述组件全部按顺序注册在BaseFieldWidget这个SPIToken组件中时,将形成一个以BaseFieldWidget为根节点的树: “` mermaidgraph TDBaseFieldWidget —> FormStringFieldWidgetBaseFieldWidget —> FormStringMultiFieldWidgetFormStringFieldWidget —> FormStringHyperlinksFieldWidget“` 树的构建 上述形成的组件树实际并非真实的存储结构,真实的存储结构是通过维度进行存储的,如下图所示: (圆角矩形表示维度上的属性和值,矩形表示对应的组件) “` mermaidgraph TDviewType([viewType: ViewType.Form]) —>ttype([ttype: ModelFieldType.Strng]) —>multi([multi: true]) & widget([widget: &#039;Hyperlinks&#039;]) direction LRttype —> FormStringFieldWidgetmulti —> FormStringMultiFieldWidgetwidget —> FormStringHyperlinksFieldWidget“` 有权重的最长路径匹配 同样以上述BaseFieldWidget组件为例,该组件可用的维度有: viewType:ViewType[Enum] ttype:ModelFieldType[Enum] multi:[Boolean] widget:[String] model:[String] viewName:[String] name:[String] 当field标签被渲染时,我们会组装一个描述当前获取维度的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget": "", // 在dsl中定义的任意值 "model": "", // 在dsl编译后自动填充 "viewName": "", // 当前视图名称 "name": "" // 字段的name属性,在dsl编译后自动填充 } 当我们需要使用FormStringHyperlinksFieldWidget这个组件时,我们在dsl中会使用如下方式定义: <view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel"> <field data="name" widget="Hyperlinks" /> </view> 此时,我们虽然没有在dsl中定义维度中的其他信息,但在dsl返回到前端时,经过了后端编译填充了对应元数据相关属性,我们可以得到如下所示的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget":…

    2023年11月1日
    69600

Leave a Reply

登录后才能评论