自定义的「视图、字段」调用界面设计器配置的按钮(包含权限控制)

我们在业务开发中,经常会遇到自定义的视图或字段。有时候期望点击某一块区域的时候,打开一个弹窗或者是跳转新页面亦或者是执行服务端动作(调接口),但是希望这个动作是界面设计器拖拽进来的。

这篇文章详细的讲解了自定义的视图、字段怎么执行界面设计器拖出来的按钮。

自定义视图

1: 先设计一个页面,把对应的动作拖拽进来,可以不需要配置字段
数式Oinone低代码-自定义的「视图、字段」调用界面设计器配置的按钮
2: 将该页面绑定菜单

3: 自定义对应的页面

当我们自定义视图的时候,首先会注册一个视图,下面是我自定义的一个表单视图

registerLayout(
  `<view type="FORM">
    <element widget="actionBar" slot="actionBar">
        <xslot name="actions" />
    </element>
    <element widget="MyWidget" slot="form">
        <xslot name="fields" />
    </element>
</view>`,
  {
    moduleName: 'ys0328',
    model: 'ys0328.k2.Model0000000453',
    actionName: 'MenuuiMenu78ec23b054314ff5a12b4fe95fe4d7b5',
    viewType: ViewType.Form
  }
);

我自定义了一个叫做MyWidget的 element,下面是对应的ts代码

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

这是对应的 vue 文件: MyVue.vue

<template>
  <div @click="onClick">点击执行动作</div>
</template>

<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    props: ['onClick']
  });
</script>

这个时候,我希望点击的时候,执行 onClick,会执行对应的动作,这时只需要在对应的 ts 文件中写对应的代码逻辑即可:

@SPI.ClassFactory(BaseElementWidget.Token({ widget: 'MyWidget' }))
export class MyWidgetManageWidget extends BaseElementWidget {
  // 获取当前页面所有的按钮
  @Widget.Reactive()
  public get modelActions() {
    return this.metadataRuntimeContext.model.modelActions || []
  }

  // 用来解析上下文表达式的,如果不需要,可以删除
  public executeCustomExpression<T>(
    parameters: Partial<ExpressionRunParam>,
    expression: string,
    errorValue?: T
  ): T | string | undefined {
    const scene = this.scene;
    return Expression.run(
      {
        activeRecords: parameters.activeRecords,
        rootRecord: parameters.rootRecord,
        openerRecord: parameters.openerRecord,
        scene: parameters.scene || scene,
        activeRecord: parameters.activeRecord
      } as ExpressionRunParam,
      expression,
      errorValue
    );
  }

  // 点击事件
  @Widget.Method()
  public onClick() {
    // 找到对应的按钮
    const action = this.modelActions.find((a) => a.label === '动作的显示名称');

     /**
    * 如果是服务端动作,就执行 executeServerAction
    */
    // executeServerAction(action, 参数对象) // 第二个参数是调用对应的接口传递的参数

 /**
    * 如果是跳转动作,就执行 executeViewAction
    */
    executeViewAction(action);

    /**
    * 如果跳转动作打开的视图需要通过id去查询数据,可以这样配置, 一般用于打开编辑、详情页
    */
    // executeViewAction(action, undefined, undefined, {id: 'xxxx'});

    /**
    * 如果跳转动作打开的视图需要需要额外的上下文参数,可以这样配置
    */
    // const _action = {
    //   ...action,
    //   context: {
    //     ...(action.context || {}),
    //     name: '123',
    //     code: '232'
    //   }
    // } as RuntimeViewAction
    // executeViewAction(_action, undefined, undefined, {id: 'xxxx'});

     /**
    * 如果跳转动作打开的视图需要上下文参数是在配置在对应动作上面,可以这样去解析上下文表达式
    */
    // const context = action.context || {}
    // const context = (action as RuntimeViewAction).context || {}
    // const parseContext = {}
    // const data = {} // 这是自己的数据源

    // Object.entries(context).forEach(([key, value]) => {
    //   if(typeof value === 'string') {
    //     parseContext[key] = this.executeCustomExpression({activeRecord: data, rootRecord: data,openerRecord: data}, value)
    //   }
    // })
    // executeViewAction({
    //   ...action,
    //   context:parseContext
    // } as RuntimeViewAction, undefined, undefined, {id: 'xxxx'});
  }

  public initialize(props: BaseElementWidgetProps): this {
    super.initialize(props);
    this.setComponent(MyVue);
    return this;
  }
}

以上我们就完成的自定义的视图如何触发界面设计器拖拽出来的按钮。

这是页面运行时的效果图

数式Oinone低代码-自定义的「视图、字段」调用界面设计器配置的按钮

数式Oinone低代码-自定义的「视图、字段」调用界面设计器配置的按钮

自定义字段

先自定义对应的字段,下面是我自定义的字段

@SPI.ClassFactory(
  BaseFieldWidget.Token({
    viewType: ViewType.Form,
    ttype: ModelFieldType.String,
    widget: 'MyField'
  })
)
export class MyFieldWidget extends FormFieldWidget {
  // 获取当前页面所有的按钮
  @Widget.Reactive()
  public get modelActions() {
    return this.metadataRuntimeContext.model.modelActions || []
  }

  // 点击事件
  @Widget.Method()
  public onClick() {
    // 找到对应的按钮
    const action = this.modelActions.find((a) => a.label === '点击打开弹窗');

    // 执行按钮
    executeViewAction(action as any);
  }

  public initialize(props: BaseElementWidgetProps): this {
    super.initialize(props);
    this.setComponent(MyVue);
    return this;
  }
}
<template>
  <div @click="onClick">点击字段打开弹窗</div>
</template>

<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    props: ['onClick']
  });
</script>

其实不管是自定义的视图还是字段,里面执行动作的写法其实是一样的。

权限控制

如果当前系统用到了权限,那么我们在执行 action 时,需要判断当前 action 是否存在,因为当用户没有该 action 权限的时候,前端是获取不到对应的 action。

@Widget.Reactive()
public get hasMyAction() {
  return this.metadataRuntimeContext.model.modelActions.find(a => a.name === 'myAction')
}
<template> <div @click="onClick" v-if="hasMyAction">点击打开弹窗</div> </template>template

我们只需要定义一个计算属性,用来处理当前 action 是否存在,最后在 vue 模版里面使用 v-if 来控制是否显示。

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

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

(0)
汤乾华的头像汤乾华数式员工
上一篇 2023年11月8日 am10:59
下一篇 2023年11月8日 pm5:05

相关推荐

  • 【前端】工程结构最佳实践(v4)

    阅读之前 你应该: 了解node与npm相关内容 了解lerna包管理工具的相关内容 官方文档 了解git仓库的相关内容 了解rollup的相关内容 工程结构包示例 Vue项目结构包下载 工程结构详解 工程结构 ├── packages │   ├── kunlun-boot │   │   ├── package.json │   │   ├── public │   │   │   ├── favicon.ico │   │   │   └── index.html │   │   ├── src │   │   │   ├── main.ts │   │   │   └── shim-vue.d.ts │   │   ├── tsconfig.json │   │   └── vue.config.js │   ├── kunlun-module-demo │   │   ├── scripts │   │   │   ├── postpublish.js │   │   │   └── prepublish-only.js │   │   ├── src │   │   │   ├── index.ts │   │   │   └── shim-vue.d.ts │   │   ├── index.ts │   │   ├── package.json │   │   ├── rollup.config.js │   │   └── tsconfig.json │   └── kunlun-modules-demo │   ├── scripts │   │   ├── build.config.js │   │   ├── postpublish.js │   │   └── prepublish-only.js │   ├── packages │   │   ├── module-demo1 │   │   │   ├── index.ts │   │   │   ├── package.json │   │   │   ├── rollup.config.js │   │   │   └── src │   │   │   ├── index.ts │   │   │   └── shim-vue.d.ts │   │   ├── module-demo2 │   │   │   ├── index.ts │   │   │   ├── package.json │   │   │   ├── rollup.config.js │   │   │  …

    前端 2023年11月1日
    1.4K00
  • 前端页面嵌套

    我们可能会遇到这些需求,如:页面中的一对多字段不是下拉框,而是另一个模型的表单组;页面中的步骤条表单,每一步的表单都需要界面设计器设计,同时这些表单可能属于不同模型。这时候我们就可以采取页面嵌套的方式,在当前页面中,动态创建一个界面设计器设计的子页面。以一对多字段,动态创建表单子页面举例,以下是代码实现和原理分析。 代码实现 AddSubformWidget 动态添加表单 ts 组件 import { ModelFieldType, ViewType, SPI, BaseFieldWidget, Widget, FormO2MFieldWidget, ActiveRecord, CallChaining, createRuntimeContextByView, queryViewDslByModelAndName, uniqueKeyGenerator } from '@oinone/kunlun-dependencies'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import { watch } from 'vue'; import AddSubform from './AddSubform.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.OneToMany, widget: 'AddSubform' }) ) export class AddSubformWidget extends FormO2MFieldWidget { public initialize(props) { super.initialize(props); this.setComponent(AddSubform); return this; } @Widget.Reactive() public myMetadataViewWidget: MyMetadataViewWidget[] = []; @Widget.Reactive() public myMetadataViewWidgetKeys: string[] = []; @Widget.Reactive() public myMetadataViewWidgetLength = 0; // region 子视图配置 public get subviewModel() { return this.getDsl().subviewModel || 'clm.contractcenter.ContractSignatory'; } public get subviewName() { return this.getDsl().subviewName || '签署方_FORM_uiViewa9c114903e104800b15e8f3749656b64'; } // region 添加子视图块 // 按钮添加点击事件 @Widget.Method() public async onAddSubviewBlock() { const resView = await queryViewDslByModelAndName(this.subviewModel, this.subviewName); this.createDynamicSubviewWidget(resView); } // 创建子视图块 public async createDynamicSubviewWidget(view, activeRecord: ActiveRecord = {}) { if (view) { // 根据视图构建上下文 const runtimeContext = createRuntimeContextByView( { type: ViewType.Form, model: view.model, modelName: view.modelDefinition.name, module: view.modelDefinition.module, moduleName: view.modelDefinition.moduleName, name: view.name, dsl: view.template }, true, uniqueKeyGenerator(), this.currentHandle ); // 取得上下文唯一标识 const runtimeContextHandle = runtimeContext.handle; const slotKey = `Form_${uniqueKeyGenerator()}`; // 创建子视图组件 const widget = this.createWidget(new MyMetadataViewWidget(runtimeContextHandle), slotKey, // 插槽名 { metadataHandle: runtimeContextHandle,…

    2025年7月21日
    95000
  • 弹窗或抽屉表单视图rootRecord获取不到对应的数据

    在平台默认的实现中,rootRecord 代表的是根视图的数据。比如,在表格页面点击按钮打开了弹窗,弹窗里面包含一个表单视图,但是该视图获取 rootRecord 却是最外层的视图数据。 如果期望 rootRecord 数据是弹窗的视图数据,需要手动修改表单的 rootRecord。下面的代码演示了如何重写 rootData 以确保其数据是弹窗的数据: @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Form, widget: 'MyCustomFormWidgetFormWidget' }) ) export class MyCustomFormWidgetFormWidget extends FormWidget { @Widget.Reactive() @Widget.Provide() public get rootData(): any[] | undefined { return this.activeRecords; } } 上述代码重写了 rootData,这样就可以确保 rootData 的数据是弹窗的数据。 接下来就是注册: registerLayout( ` <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="MyCustomFormWidgetFormWidget" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> `, { viewType: ViewType.Form, model: '弹窗模型', viewName: '弹窗视图名称' } )

    2023年11月13日
    1.8K00
  • 在前端视图添加自定义的区域块

    添加自定义区域块 平台提供了一系列默认的视图布局,可以帮助开发人员快速构建出复杂的企业应用系统。当然,我们可以使用自定义区域块来扩展表格、表单、画廊、树形等视图。 自定义区域块概述 平台视图布局都是通过XML配置实现的。在视图布局中,我们可以使用一些特定的元素标签来构建视图的表头、表单、搜索区域等部分。而自定义区域块,就是这些元素标签之外的部分。我们可以通过在视图布局的XML中添加自定义区域块,来扩展页面功能。 视图类型及相关元素 视图类型分为表格(TABLE)、表单(FORM)、画廊(GALLERY)、树形(TREE)等。不同类型的视图布局,包含的元素也有所不同。 下面是几种视图类型及其对应的元素: 表格:搜索区域、表格主体,其中表格主体包含了表格上面的动作、表格区域等部分。 表单:表单区域,包含了表单动作、表单区域等部分。 画廊:动作、卡片详细信息。 在表格页面添加自定义区域块 以下是一个示例,演示如何在表格页面顶部添加自定义区域块。 1. 修改视图布局XML 首先,我们需要修改表格视图的XML布局,添加自定义区域块元素标签。 <view type="TABLE"> <!– 这是搜索区域 –> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <!– 这是表格主体 –> <pack widget="group" slot="tableGroup"> <!– 在这里添加自定义区域块元素标签 –> <element widget="MyCustomElement"></element> <!– 这是表格上面的动作 –> <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> 在上述代码中,我们添加了一个名为MyCustomElement的元素标签。这将作为我们自定义区域块的容器。 2. 创建自定义Vue组件 接下来,我们需要创建一个Vue组件,并将其指定为自定义元素标签MyCustomElement的模板。 <template> <div> <!– 在这里编写自定义区域块的内容 –> <p>Hello, world!</p> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ components: { }, props: [], setup(props) { return {}; } }); </script> 在上述代码中,我们定义了一个非常简单的Vue组件,它在页面上显示一个“Hello, world!”的文本信息。 3. 创建自定义Element Widget 为了使自定义Vue组件与XML布局文件关联起来,我们需要创建一个对应的Element Widget。 import { BaseElementWidget, SPI, BaseElementViewWidget, Widget, ViewMode, FormWidget, BaseElementWidgetProps } from '@kunlun/dependencies'; import MyCustomElement from './MyCustomElement.vue'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'MyCustomElementWidget' })) export class MyCustomElementWidget extends BaseElementWidget { public initialize(props: BaseElementWidgetProps): this { super.initialize(props) this.setComponent(MyCustomElement) return this } } 在上述代码中,我们继承了BaseElementWidget类,并在其中指定了Vue组件MyCustomElement。这样,XML布局文件中的元素标签就能够正确地与Vue组件关联起来。 4. 注册视图布局 最后,我们需要将上述代码配置注册。具体而言,我们需要调用registerLayout方法来将XML布局文件、模块名和视图类型进行关联。 import { registerLayout, ViewType } from '@kunlun/dependencies'; registerLayout( `<view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" />…

    2023年11月1日
    2.8K00
  • 如何自定义 GraphQL 请求

    在开发过程中,有时需要自定义 GraphQL 请求来实现更灵活的数据查询和操作。本文将介绍两种主要的自定义 GraphQL 请求方式:手写 GraphQL 请求和调用平台 API。 方式一:手写 GraphQL 请求 手写 GraphQL 请求是一种直接编写查询或变更语句的方式,适用于更复杂或特定的业务需求。以下分别是 query 和 mutation 请求的示例。 1. 手写 Query 请求 以下是一个自定义 query 请求的示例,用于查询某个资源的语言信息列表。 const customQuery = async () => { const query = `{ resourceLangQuery { queryListByEntity(query: {active: ACTIVE, installState: true}) { id name active installState code isoCode } } }`; const result = await http.query('resource', query); this.list = result.data['resourceLangQuery']['queryListByEntity']; }; 说明: query 语句定义了一个请求,查询 resourceLangQuery 下的语言信息。 查询的条件是 active 和 installState,只返回符合条件的结果。 查询结果包括 id、name、active、installState 等字段。 2. 手写 Mutation 请求 以下是一个 mutation 请求的示例,用于创建新的资源分类信息。 const customMutation = async () => { const code = Date.now() const name = `测试${code}` const mutation = `mutation { resourceTaxKindMutation { create(data: {code: "${code}", name: "${name}"}) { id code name createDate writeDate createUid writeUid } } }`; const res = await http.mutate('resource', mutation); console.log(res); }; 说明: mutation 语句用于创建一个新的资源分类。 create 操作的参数是一个对象,包含 code 和 name 字段。 返回值包括 id、createDate 等字段。 方式二:调用平台的 API 平台 API 提供了简化的 GraphQL 调用方法,可以通过封装的函数来发送 query 和 mutation 请求。这种方式减少了手写 GraphQL 语句的复杂性,更加简洁和易于维护。 1. 调用平台的 Mutation API 使用平台的 customMutation 方法可以简化 Mutation 请求。 /** * 自定义请求方法 * @param modelModel 模型编码 * @param method 方法名或方法对象 * @param records 请求参数,可以是单体对象或者对象的列表 * @param requestFields…

    2024年9月21日
    1.9K00

Leave a Reply

登录后才能评论