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

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

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

自定义视图

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低代码应用平台体验

Like (0)
汤乾华's avatar汤乾华数式员工
Previous 2023年11月8日 am10:59
Next 2023年11月8日 pm5:05

相关推荐

  • 前端自定义组件之多页面步骤条

    本文将讲解如何通过自定义,实现多页面的步骤条组件。其中每个步骤的元素里都对应界面设计器的一个页面。以下是代码实现和原理分析。 代码实现 NextStepWidget 多页面步骤条 ts 组件 import { CallChaining, createRuntimeContextByView, customMutation, customQuery, RuntimeView, SPI, ViewCache, Widget, DefaultTabsWidget, BasePackWidget } from '@oinone/kunlun-dependencies'; import { isEmpty } from 'lodash-es'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import NextStep from './NextStep.vue'; import { IStepConfig, StepDirection } from './type'; @SPI.ClassFactory(BasePackWidget.Token({ widget: 'NextStep' })) export class NextStepWidget extends DefaultTabsWidget { public initialize(props) { this.titles = props.template?.widgets?.map((item) => item.title) || []; props.template && (props.template.widgets = []); super.initialize(props); this.setComponent(NextStep); return this; } @Widget.Reactive() public get invisible() { return false; } // 配置的每一步名称 @Widget.Reactive() public titles = []; // region 上一步下一步配置 // 步骤配置,切换的顺序就是数组的顺序,模型没有限制 @Widget.Reactive() public get stepJsonConfig() { let data = JSON.parse( this.getDsl().stepJsonConfig || '[{"model":"resource.ResourceCountry","viewName":"国家form"},{"model":"resource.ResourceProvince","viewName":"省form"},{"model":"resource.ResourceCity","viewName":"市form"}]' ); return data as IStepConfig[]; } // 切换上一步下一步 @Widget.Method() public async onStepChange(stepDirection: StepDirection) { // 没有激活的,说明是初始化,取第一步 if (!this.activeStepKey) { const step = this.stepJsonConfig[0]; if (step) { this.activeStepKey = `${step.model}_${step.viewName}`; await this.initStepView(step); } return; } // 获取当前步骤的索引 if (this.currentActiveKeyIndex > -1) { await this.onSave(); // 获取下一步索引 const nextStepIndex = stepDirection === StepDirection.NEXT ? this.currentActiveKeyIndex + 1 : this.currentActiveKeyIndex – 1; // 在索引范围内,则渲染视图 if (nextStepIndex >= 0 && nextStepIndex < this.stepJsonConfig.length) { const nextStep = this.stepJsonConfig[nextStepIndex];…

    2025年7月21日
    88200
  • 组件生命周期(v4)

    阅读之前: 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 对第三方框架的组件生命周期有所了解。如Vue组件生命周期 了解平台实现的Class Component(ts)相关内容。Class Component(ts)(v4) 组件生命周期 任何一个Widget其标准生命周期应当包括beforeCreated、created、beforeMount、mounted、beforeUnmount、unmounted这六个基本的生命周期函数、以及beforeUpdate和updated在响应式更新时会进行调用的生命周期函数。特别的,还有activated和deactivated在组件搭配keep-alive特性时使用的生命周期函数。 具体的生命周期执行过程在这里不再进行赘述,这里的基本逻辑与Vue组件生命周期基本完全一致,感兴趣的读者可以阅读Vue相关文档进行学习。

    2023年11月1日
    1.8K10
  • 前端自定义组件之左右滑动

    本文将讲解如何通过自定义,实现容器内的左右两个元素,通过左右拖拽分隔线,灵活调整宽度。其中左右元素里的内容都是界面设计器拖出来的。 实现路径 1. 界面设计器拖出页面 我们界面设计器拖个布局容器,然后在左右容器里拖拽任意元素。完成后点击右上角九宫格,选中布局容器,填入组件 api 名称,作用是把布局容器切换成我们自定义的左右滑动组件,这里的 api 名称和自定义组件的 widget 对应。最后发布页面,并绑定菜单。 2. 组件实现 widget 组件重写了布局容器,核心函数 renderLeft、renderRight,通过 DslRender.render 方法渲染界面设计器拖拽的元素。 import { BasePackWidget, DefaultContainersWidget, DslDefinition, DslRender, SPI, Widget } from '@oinone/kunlun-dependencies'; import LeftRightSlide from './LeftRightSlide.vue'; // 拿到界面设计器配置的子容器元素 function fetchContainerChildren(widgets?: DslDefinition[], level = 3): DslDefinition[] { if (!widgets) { return []; } const children: DslDefinition[] = []; for (const widget of widgets) { if (widget.widget === 'container') { children.push(widget); } else if (level >= 1) { fetchContainerChildren(widget.widgets, level – 1).forEach((child) => children.push(child)); } } return children; } @SPI.ClassFactory(BasePackWidget.Token({ widget: 'LeftRightSlide' })) export class LeftRightSlideWidget extends DefaultContainersWidget { public initialize(props) { super.initialize(props); this.setComponent(LeftRightSlide); return this; } // 获取容器的子元素 public get containerChildren(): DslDefinition[] { return fetchContainerChildren(this.template?.widgets); } // 初始宽度配置 @Widget.Reactive() public get initialLeftWidth() { return this.getDsl().initialLeftWidth || 400; } // 最小左宽度配置 @Widget.Reactive() public get minLeftWidth() { return this.getDsl().minLeftWidth || 200; } // 最小右宽度配置 @Widget.Reactive() public get minRightWidth() { return this.getDsl().minRightWidth || 200; } // 根据容器子元素渲染左侧 @Widget.Method() public renderLeft() { // 把容器的第一个元素作为左侧 const containerLeft = this.containerChildren[0]; if (containerLeft) { return DslRender.render(containerLeft); } } // 根据容器子元素渲染右侧 @Widget.Method() public renderRight() { // 把容器的第二个元素作为右侧 const containerRight = this.containerChildren[1]; if…

    2025年7月8日
    4.4K00
  • oinone的GraphQL使用指南

    如果之前没了解过GraphQL,可以先查看GraphQL的文档 为什么oinone要选用GraphQL? 我们先看一下oinone独特的元数据设计 介绍信息来源于Oinone 7天从入门到精通,如提示无权限,则需要申请 再看一下GraphQl的介绍 我们可以看出,GraphQL提供的特性可以满足我们对元数据的描述需求,因此我们选用GraphQL。 相关工具推荐 可视化gql请求工具: 官方下载地址 oinone工具包-win版 oinone工具包-mac版 模拟登录请求 点击“Refresh Schema”按钮手动同步后端的gql文档数据 点击“show Documentation”按钮查看gql文档,可以在搜索框内输入关键字查询相关文档

    2023年11月1日
    1.8K00
  • 树型表格全量加载数据如何处理

    阅读该文档的前置条件 【界面设计器】树形表格 1.前端自定义表格组件 import { ActiveRecord, BaseElementWidget, Condition, Entity, SPI, TableWidget, ViewType } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ type: ViewType.Table, widget: ['demo-tree-table'] }) ) export class TreeTableWidget extends TableWidget { // 默认展开所有层级 protected getTreeExpandAll() { return true; } // 关闭懒加载 protected getTreeLazy(): boolean { return false; } public async $$loadTreeNodes(condition?: Condition, currentRow?: ActiveRecord): Promise<Entity[]> { // 树表加载数据的方法,默认首次只查第一层的数据,这里去掉这个查询条件的参数condition,这样就会查所有层级数据 return super.$$loadTreeNodes(undefined, currentRow); } } 2. 注册layout import { registerLayout, ViewType } from '@kunlun/dependencies'; const install = () => { registerLayout( ` <view type="TABLE"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="demo-tree-table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </view> `, { viewType: ViewType.Table, model: "resource.resourceCity", // 变量,需要替换 actionName: "MenuuiMenu6f6005bdddba468bb2fb814a62fa83c6", // 变量,需要替换 } ); }; install();

    2024年8月17日
    1.6K00

Leave a Reply

Please Login to Comment