自定义组件之手动渲染弹出层(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

相关推荐

  • oio-modal 对话框

    API 参数 说明 类型 默认值 版本 cancelText 取消按钮文字 string| slot 取消 closable 是否显示右上角的关闭按钮 boolean true closeIcon 自定义关闭图标 VNode | slot – confirmLoading 确定按钮 loading boolean 无 destroyOnClose 关闭时销毁 Modal 里的子元素 boolean false footer 底部内容,当不需要默认底部按钮时,可以设为 :footerInvisible="true" slot 确定取消按钮 getTriggerContainer 指定 Modal 挂载的 HTML 节点 (instance): HTMLElement () => document.body keyboard 是否支持键盘 esc 关闭 boolean true mask 是否展示遮罩 boolean true maskClosable 点击蒙层是否允许关闭 boolean true enterText 确认按钮文字 string 确定 title 标题 string|slot 无 visible(v-model:visible) 对话框是否可见 boolean 无 width 宽度 string|number 520 wrapClassName 对话框外层容器的类名 string – zIndex 设置 Modal 的 z-index number 1000 cancelCallback 点击遮罩层或右上角叉或取消按钮的回调, return true则关闭弹窗 function(e) enterCallback 点击确定回调 function(e)

    2023年12月18日
    1.7K00
  • 表格字段配置Switch开关

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

    2023年11月21日
    2.1K00
  • 多对多的表格 点击添加按钮打开一个表单弹窗

    多对多的表格 点击添加按钮打开一个表单弹窗 默认情况下,多对多的表格上方的添加按钮点击后,打开的是个表格 ,如果您期望点击添加按钮打开的是个表单页面,那么可以按照下方的操作来 1: 先从界面设计器拖一个多对多的字段进来 2: 将该字段切换成表格,并拖入一些字段到表格上 3: 选中添加按钮,将其隐藏 4: 从组件区域的动作分组中拖一个跳转动作,并且进行如下的配置 5: 属性填写好后进行保存,然后在设计弹窗 6: 拖入对应的字段到弹窗中, 当弹窗界面设计完成后,再把保存的按钮拖入进来 这样多对多的添加弹窗就变成了表单

    2023年11月9日
    1.5K00
  • 组件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日
    1.2K00
  • 如何通过浏览器开发者工具提高调试效率

    1.通过vue devtool查看vue组件和oinone组件的信息 平台字段、动作、视图组件都是以Widget结尾的,可以通过关键字快速找到组件 安装vue devtool插件 chrome安装最新版的vue devtool插件 谷歌应用商店插件地址,隐藏窗口需要在扩展程序的详情页额外设置才能使用该插件 安装好插件后,可以通过插件选中html页面中的元素查看相关信息 相关特性了解 组件自动创建的vue组件以组件的class类名命名,据此特性可以得出自定义组件的时候,可继承该命名的父类 一般oinone的组件以Widget后缀命名,也推荐大家也以此为后缀,方便在vue调试面板快速看出哪些是oinone组件 选中oinone组件后可以在右侧面板看到相关的组件信息,部分属性介绍 template属性为dsl的配置 activeRecords、formData、rootData、parentData、dataSource等属性为常用数据属性 action为动作的元数据 field为字段的元数据 2.直接在浏览器开发者工具的源码处调试源码 打开浏览器开发者工具,进入标签页源代码/来源(英文版为Source),win系统用快捷键ctrl+O,mac系统用快捷键cmd+O, 可以打开文件搜索面板,通过关键字搜索文件名找到代码文件,可直接在里面debug调试或者查看执行逻辑

    2024年9月9日
    1.8K00

Leave a Reply

登录后才能评论