如何自定义点击导出动作绑定指定模板

介绍

平台默认的导出会打开弹窗,然后在弹窗内的视图选择是用模板方式导出还是选字段导出,但是有时候有部分场景希望点击导出动作后直接进入导出流程,导出指定的某个模板,我们可以通过覆写打开弹窗的动作来实现该功能。

本文档参考了 表格页自定义按钮如何获取搜索区域的查询条件

代码示例

以下代码大部分场景只需要修改其中excelTplName更换模板即可,另外如何想增加复用性,还可以将该属性改为从元数据的配置中获取。

import {
  ActionType,
  BaseActionWidget,
  BaseElementListViewWidget,
  BooleanHelper,
  ClickResult,
  Condition,
  ExcelExportTask,
  FILE_MODULE_NAME,
  getSessionPath,
  GraphqlHelper,
  http,
  IQueryPageResult,
  ISort,
  queryDslWidget,
  ReturnPromise,
  RuntimeServerAction,
  ServerActionWidget,
  SPI,
  SubmitValue,
  SYSTEM_MODULE_NAME,
  translateValueByKey,
  UrlHelper,
  ViewActionTarget,
  Widget
} from '@kunlun/dependencies';
import { OioNotification } from '@kunlun/vue-ui-antd';

@SPI.ClassFactory(
  BaseActionWidget.Token({
    actionType: [ActionType.View],
    target: [ViewActionTarget.Dialog],
    model: 'ys0328.k2.Model0000000453',
    name: 'internalGotoListExportDialog'
  })
)
export class DemoExportActionWidget extends ServerActionWidget {
  /**
   * excel导出模板名称
   * @protected
   */
  protected excelTplName = '演示抽屉跳转链接导出';

  /**
   * 导出任务的模型编码
   * @protected
   */
  protected exportTaskModel = 'excelExportTask';

  /**
   * 导出任务的方法
   * @protected
   */
  protected exportTaskFun = 'createExportTask';

  /**
   *
   * 是否是同步导出
   */
  @Widget.Reactive()
  protected get syncExport() {
    return BooleanHelper.isTrue(this.getDsl().sync) || !true;
  }

  protected async executeAction(action: RuntimeServerAction, parameters: SubmitValue): Promise<ClickResult> {
    const workbookId = await this.getWorkbookId();
    if (!workbookId) {
      return false;
    }
    let task = {
      workbookDefinition: {
        id: workbookId
      }
    } as ExcelExportTask;

    // 从平台内置的方法获取搜索区域的条件
    const { condition } = this.getSearchRsqlAndQueryParams();

    // 排序规则
    let sortList = [] as ISort[];
    const baseViewWidget = Widget.select(this.rootHandle);
    const listViewWidget = queryDslWidget(baseViewWidget?.getChildrenInstance(), BaseElementListViewWidget);
    if (listViewWidget) {
      sortList = listViewWidget.sortList;
    }

    return this.export(task, condition, sortList);
  }

  protected getUploadBodyGql(id: string, condition: string | Condition, sortList: ISort[]) {
    let queryData = '{}';
    let rsql = condition;
    if (condition instanceof Condition) {
      const conditionBodyData = condition.getConditionBodyData();
      if (conditionBodyData && Object.keys(conditionBodyData).length) {
        queryData = GraphqlHelper.serializableObject(conditionBodyData);
      }
      rsql = condition.toString();
    }

    const sortListStr = (sortList || []).map((sort) => `{field: "${sort.sortField}", direction: ${sort.direction}}`).join(',');
    const sorts = sortListStr ? `sort: {orders: [${sortListStr}]}` : '';
    const conditionWrapper = condition
      ? `,conditionWrapper:{
            rsql: "${rsql}",
            queryData: "${queryData}"
            ${sorts}
          }`
      : '';
    const mutation = `mutation {
      ${this.exportTaskModel}Mutation {
        ${this.exportTaskFun}(data: {sync: ${this.syncExport}, workbookDefinition: {id: ${id}}${conditionWrapper}}) {
          id
          name
          state
        }
      }
    }`;

    return mutation;
  }

  protected async export(task: ExcelExportTask, condition: Condition | string, sortList: ISort[]) {
    let gql = await this.getUploadBodyGql(task.workbookDefinition!.id, condition, sortList);
    const variables = {
      path: getSessionPath()
    };
    let responseResult = false;
    if (this.syncExport) {
      window.open(
        UrlHelper.appendBasePath(
          `/pamirs/${FILE_MODULE_NAME}?query=${encodeURIComponent(gql)}&variables=${encodeURIComponent(
            JSON.stringify(variables)
          )}`
        ),
        '_blank'
      );
      responseResult = true;
    } else {
      const res = await http.mutate(SYSTEM_MODULE_NAME.FILE, gql, variables);
      const data = res.data[`${this.exportTaskModel}Mutation`][this.exportTaskFun] as any;
      responseResult = !!data.id;
    }
    if (responseResult) {
      OioNotification.success(this.translate('kunlun.common.success'));
      return true;
    }
    OioNotification.error(this.translate('kunlun.common.error'));
    return false;
  }

  protected async getWorkbookId(): ReturnPromise<string | undefined> {
    const model = this.model.model!;
    const gql = `{
  excelWorkbookDefinitionQuery {
    queryPage(
      page: {currentPage: 1, size: 1}
      queryWrapper: {rsql: "displayName=='${this.excelTplName}' and model == '${model}' and dataStatus == 'ENABLED' and type =in= ('IMPORT_EXPORT','EXPORT')"}
    ) {
      content {
        displayName
        id
      }
      totalPages
      totalElements
    }
  }
}
`;
    const variables = {
      path: getSessionPath()
    };
    const res = await http.query(SYSTEM_MODULE_NAME.FILE, gql, variables);
    const pageRes = res.data.excelWorkbookDefinitionQuery.queryPage as unknown as IQueryPageResult<any>;
    const id = pageRes.content?.[0]?.id;
    if (!id) {
      OioNotification.warning(translateValueByKey('没有找到匹配的导出模板'));
    }
    return id;
  }
}

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

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

Like (0)
nation's avatarnation数式员工
Previous 2024年10月8日 pm6:41
Next 2024年10月10日 am10:35

相关推荐

  • 自定义组件之自动渲染(组件插槽的使用)(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 了解SPI机制相关内容。组件SPI机制(v4.3.0) 自定义组件简介 前面我们简单介绍过一个简单的自定义组件该如何被定义,并应用于页面中。这篇文章将对自定义组件进行详细介绍。 自定义一个带有具名插槽的容器组件(一般用于Object数据类型的视图中) 使用BasePackWidget组件进行注册,最终体现在DSL模板中为<pack widget="SlotDemo">。 SlotDemoWidget.ts import { BasePackWidget, SPI } from '@kunlun/dependencies'; import SlotDemo from './SlotDemo.vue'; @SPI.ClassFactory(BasePackWidget.Token({ widget: 'SlotDemo' })) export class SlotDemoWidget extends BasePackWidget { public initialize(props) { super.initialize(props); this.setComponent(SlotDemo); return this; } } 定义一个Vue组件,包含三个插槽,分别是default不具名插槽、title具名插槽、footer具名插槽。 SlotDemo.vue <template> <div class="slot-demo-wrapper" v-show="!invisible"> <div class="title"> <slot name="title" /> </div> <div class="content"> <slot /> </div> <div class="footer"> <slot name="footer" /> </div> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ name: 'SlotDemo', props: { invisible: { type: Boolean, default: undefined } } }); </script> 在一个表单(FORM)的DSL模板中,我们可以这样使用这三个插槽: <view type="FORM"> <pack widget="SlotDemo"> <template slot="default"> <field data="id" /> </template> <template slot="title"> <field data="name" /> </template> <template slot="footer"> <field data="isEnabled" /> </template> </pack> </view> 这样定义的一个组件插槽和DSL模板就进行了渲染上的结合。 针对不具名插槽的特性,我们可以缺省slot="default"标签,缺少template标签包裹的所有元素都将被收集到default不具名插槽中进行渲染,则上述DSL模板可以改为: <view type="FORM"> <pack widget="SlotDemo"> <field data="id" /> <template slot="title"> <field data="name" /> </template> <template slot="footer"> <field data="isEnabled" /> </template> </pack> </view> 自定义一个数组渲染组件(一般用于List数据类型的视图中) 由于表格无法体现DSL模板渲染的相关能力,因此我们以画廊视图(GALLERY)进行演示。 先定义一个数组每一项的数据结构: typing.ts export interface ListItem { key: string; data: Record<string, unknown>; index: number; } ListRenderDemoWidget.ts import { BaseElementListViewWidget, BaseElementWidget, SPI } from '@kunlun/dependencies'; import ListRenderDemo from './ListRenderDemo.vue'; @SPI.ClassFactory(BaseElementWidget.Token({ widget: 'ListRenderDemo' })) export class ListRenderDemoWidget extends BaseElementListViewWidget { public initialize(props)…

    2023年11月1日
    1.2K00
  • 前端表格复制

    我们可能会遇到表格复制的需求,也就是表格填写的时候,不是增加一行数据,而是增加一个表格。以下是代码实现和原理分析。 代码实现 在 boot 工程的 main.ts 中加入以下代码 import { registerDesignerFieldWidgetCreator, selectorDesignerFieldWidgetCreator } from '@oinone/kunlun-ui-designer-dependencies'; // 注册无代码组件,将表头分组的无代码组件,注册成字段表格组件 registerDesignerFieldWidgetCreator( { widget: 'DynamicCreateTable' }, selectorDesignerFieldWidgetCreator({ widget: TABLE_WIDGET })! ); DynamicCreateTableWidget 动态添加表格 ts 组件 import { FormO2MTableFieldWidget, Widget, DslDefinition, RuntimeView, SubmitValue, BaseFieldWidget, ModelFieldType, SPI, ViewType, ActiveRecord, uniqueKeyGenerator } from '@oinone/kunlun-dependencies'; import { MyMetadataViewWidget } from './MyMetadataViewWidget'; import DynamicCreateTable from './DynamicCreateTable.vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.OneToMany, widget: 'DynamicCreateTable' }) ) export class DynamicCreateTableWidget extends FormO2MTableFieldWidget { public myMetadataViewWidget: MyMetadataViewWidget[] = []; @Widget.Reactive() public myMetadataViewWidgetLength = 0; @Widget.Reactive() public myMetadataViewWidgetKeys: string[] = []; protected props: Record<string, unknown> = {}; public initialize(props) { super.initialize(props); this.props = props; this.setComponent(DynamicCreateTable); return this; } // region 创建动态表格 @Widget.Method() public async createTableWidget(record: ActiveRecord) { const index = this.myMetadataViewWidget.length; const handle = uniqueKeyGenerator(); const slotKey = `MyMetadataViewWidget_${handle}`; const widget = this.createWidget( new MyMetadataViewWidget(handle), slotKey, // 插槽名称 { subIndex: index, metadataHandle: this.metadataHandle, rootHandle: this.rootHandle, automatic: true, internal: true, inline: true } ); this.initDynamicSubview(this.props, widget); widget.setData(record); this.myMetadataViewWidgetLength++; this.myMetadataViewWidgetKeys.push(slotKey); this.myMetadataViewWidget.push(widget); } protected initDynamicSubview(props: Record<string, unknown>, widget: MyMetadataViewWidget) { const { currentViewDsl } = this; let viewDsl = currentViewDsl; if (!viewDsl) { viewDsl = this.getViewDsl(props)…

    2025年7月21日
    87500
  • 【界面设计器】自定义字段组件实战——轮播图

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 用户需要从【创建/编辑】页面中上传多张图片,并且在【详情】页面将这多张图片进行【轮播】展示。 业务分析 从需求来看,我们需要实现一个【轮播图】组件,并且该组件允许在【详情】视图中使用。在其他视图中,我们可以直接使用平台内置的【图片】组件,实现基础的编辑和展示功能。 名词解释 业务模型:需要进行可视化管理的存储模型或代理模型。 准备工作 你需要在某个业务模型下创建一个【表格视图】用于查看全部数据,创建【表单视图】用于创建/编辑数据,并创建【详情视图】展示必要的信息。(为了方便起见,你可以在所有视图中仅使用编码和名称两个字段) 你需要将【表格视图】绑定到某个菜单上,并通过【跳转动作】将三个视图进行关联,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) DemoModel 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 轮播图 carouselImages 文本 是 512 实现页面效果展示 表格视图 表单视图-创建 表单视图-编辑 详情视图 根据业务背景添加轮播图字段到所有视图 轮播图字段信息: 字段业务类型:文本 多值:是 使用组件:图片 无代码模型 在模型设计器创建轮播图字段,并从【组件库】-【模型】拖放至视图中即可。 PS:这里需要注意的是,在模型设计器中需要切换至专家模式,并确认字段长度为512,否则当URL超长时将无法保存。 低代码模型 与服务端同学确认字段,并从【组件库】-【模型】中拖放至视图中即可。 将上图中的【演示】数据进行【编辑】,并上传三张图片,在【详情视图】查看默认展示效果。 演示图片下载 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建轮播图组件 创建轮播图元件 根据业务背景,我们需要根据模型中的字段确定业务类型,在这个场景中,可以使用如下配置。(暂时可以不进行属性面板的设计) 在【详情视图】中将【轮播图字段】的组件切换为我们新创建的【轮播图组件】 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文件在【界面设计器】的【低无一体】进行上传,就可以在【设计器环境】中正常使用了。 设计轮播图的属性面板 通过我们使用的a-carousel组件,我们发现组件中提供了很多【属性】或【功能】可以进行配置,比如是否自动切换(autoplay)、面板指示点位置(dotPosition)、是否显示面板指示点(dots)等。在这里我们将对这三个属性的配置方式进行演示,其他更多属性可以自行设计并开发。 我们可以在【界面设计器】的【属性面板设计】中根据这三个属性的字段类型确定以下信息: 功能 API名称 业务类型 选用组件 可选项 是否自动切换 autoplay 布尔 开关 -…

    2023年11月1日
    4.5K00
  • 如何自定义表格字段?

    目录 一、表单字段注册vue组件实现机制 二、表格字段注册vue组件实现机制 三、机制对比分析 四、表格字段完整案例 表单字段注册vue组件实现机制 核心代码 public initialize(props) { super.initialize(props); this.setComponent(VueComponent); return this; } 表格字段注册vue组件实现机制 核心代码 @Widget.Method() public renderDefaultSlot(context: RowContext) { const value = this.compute(context); return [createVNode(CustomTableString, { value })]; } 因为表格有行跟列,每一列都是一个单独的字段(对应的是TS文件),但是每列里面的单元格承载的是Vue组件,所以通过这种方式可以实现表格每个字段对应的TS文件是同一份,而单元格的组件入口是通过renderDefaultSlot函数动态渲染的vue组件,只需要通过createVNode创建对应的vue组件,然后将props传递进去就行 上下文接口 interface RowContext<T = unknown> { /** * 当前唯一键, 默认使用__draftId, 若不存在时,使用第三方组件内置唯一键(如VxeTable使用{@link VXE_TABLE_X_ID}) */ key: string; /** * 当前行数据 */ data: Record<string, unknown>; /** * 当前行索引 */ index: number; /** * 第三方组件原始上下文 */ origin: T; } 机制对比分析 表单字段 vs 表格字段渲染机制对比表 对比维度 表单字段实现方案 表格字段实现方案 绑定时机 initialize 阶段静态绑定 renderDefaultSlot 阶段动态创建 组件声明方式 this.setComponent(Component) createVNode(Component, props) 上下文传递 通过类成员变量访问 显式接收 RowContext 参数 渲染控制粒度 字段级(表单控件) 单元格级 表格字段完整案例 import { SPI, ViewType, BaseFieldWidget, Widget, TableNumberWidget, ModelFieldType, RowContext } from '@kunlun/dependencies'; import CustomTableString from './CustomTableString.vue'; import { createVNode } from 'vue'; @SPI.ClassFactory( BaseFieldWidget.Token({ ttype: ModelFieldType.String, viewType: [ViewType.Table], widget: 'CustomTableStringWidget' }) ) export class CustomTableStringWidget extends BaseTableFieldWidget { @Widget.Method() public renderDefaultSlot(context:RowContext) { const value = this.compute(context); // 当前字段的值 const rowData = context.data // 当前行的数据 const dataSource = this.dataSource // 表格数据 if (value) { // 自定义组件入口在此处 return [createVNode(CustomTableString, { value })]; } return []; } } <template> <div>当前值: {{value}}</div> </template> <script lang="ts"> import { defineComponent } from 'vue'…

    2023年11月6日
    2.5K00
  • 前端低无一体使用教程

    介绍 客户在使用oinone平台的时候,有一些个性化的前端展示或交互需求,oinone作为开发平台,不可能提前预置好一个跟客户需求一模一样的组件,这个时候我们提供了一个“低无一体”模块,可以反向生成API代码,生成对应的扩展工程和API依赖包,再由专业前端研发人员基于扩展工程(kunlun-sdk),利用API包进行开发并上传至平台,这样就可以在界面设计器的组件里切换为我们通过低无一体自定义的新组件。 低无一体的具体体现 “低”— 指低代码,在sdk扩展工程内编写的组件代码 “无”— 指无代码,在页面设计器的组件功能处新建的组件定义 低无一体的组件跟直接在自有工程内写组件的区别? 低无一体的组件复用性更高,可以在本工程其他页面和其他业务工程中再次使用。 组件、元件介绍 元件 — 指定视图类型(viewType) + 指定业务类型(ttype)字段的个性化交互展示。组件 — 同一类个性化交互展示的元件的集合。组件是一个大一点的概念,比如常用的 Input 组件,他的元件在表单视图有字符串输入元件、密码输入元件,在详情和表格展示的时候就是只读的,页面最终使用的其实是元件。通过组件+ttype+视图类型+是否多值+组件名可以找到符合条件的元件,组件下有多个元件会根据最优匹配规则找到最合适的具体元件。 如何使用低无一体 界面设计器组件管理页面添加组件 进入组件的元件管理页面 点击“添加元件” 设计元件的属性 这里以是否“显示清除按钮”作为自定义属性从左侧拖入到中间设计区域,然后发布 点击“返回组件” 鼠标悬浮到卡片的更多按钮的图标,弹出下拉弹出“低无一体”的按钮 在弹窗内点击“生成SDK”的按钮 生成完成后,点击“下载模板工程” 解压模板工程kunlun-sdk.zip 解压后先查看README.MD,了解一下工程运行要点,可以先运行 npm i 安装依赖 再看kunlun-plugin目录下已经有生成好的组件对应的ts和vue文件 下面在vue文件内增加自定义代码,可以运行 npm run dev 在开发模式下调试看效果 <template> <div class="my-form-string-input"> <oio-input :value="realValue" @update:value="change" > <template #prepend>MyPrepend</template> </oio-input> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import { OioInput } from '@kunlun/vue-ui-antd'; export default defineComponent({ name: 'customField1', components: { OioInput }, props: { value: { type: String }, change: { type: Function }, }, setup(props) { const realValue = ref<string | null | undefined>(props.value); return { realValue }; } }); </script> <style lang="scss"> .my-form-string-input { border: 1px solid red; } </style> 确定改好代码后运行 npm run build,生成上传所需的js和css文件 可以看到 kunlun-plugin目录下多出了dist目录,我们需要的是 kunlun-plugin.umd.js 和 kunlun-plugin.css 这2个文件 再次回到组件的“低无一体”管理弹窗页面,上传上面生成的js和css文件,并点击“确定”保存,到这里我们的组件就新增完成了。 下面我们再到页面设计器的页面中使用上面设计的组件(这里的表单页面是提前准备好的,这里就不介绍如何新建表单页面了) 将左侧组件库拉直最底部,可以看到刚刚新建的组件,将其拖至中间设计区域,我们可以看到自定义组件的展示结果跟刚刚的代码是对应上的(ps: 如果样式未生效,请刷新页面查看,因为刚刚上传的js和css文件在页面初始加载的时候才会导入进来,刚刚上传的动作未导入新上传的代码文件),再次点击设计区域中的自定义组件,可以看到右侧属性设置面板也出现了元件设计时拖入的属性。 最后再去运行时的页面查看效果,与代码逻辑一致! 注意事项 为什么我上传了组件后页面未生效? 检查前端启动工程的低无一体是否配置正确

    2023年11月6日
    3.7K00

Leave a Reply

Please Login to Comment