自定义视图内部渲染动态视图

效果图

自定义视图内部渲染动态视图

当前图片中,上方是自定义的视图,下方是动态的表单视图

代码

步骤拆分:
1: 通过注册 layout 的方式先自定义视图,把自己的业务逻辑写完
2: 在对应的 vue 文件里面定义一个插槽,用来放置动态的表单视图
3: 在组件挂在的时候,创建动态视图

1: 注册 layout

// registry.ts

import { registerLayout, ViewType } from '@kunlun/dependencies';

registerLayout(
  `<view type="FORM">
    <element widget="actionBar" slot="actionBar" slotSupport="action">
        <xslot name="actions" slotSupport="action" />
    </element>
    <element widget="CustomViewWidget"></element>
</view>`,
  {
    model: '模型编码',
    actionName: '动作名称',
    viewType: ViewType.Form
  }
);

2: vue 里面定义 slot

<!--CustomView.vue -->

<template>
  <div>
    <h1>这是自定义的视图</h1>
    <img
      src="https://pamirs.oss-cn-hangzhou.aliyuncs.com/oinone/static/images/login_bg_left.jpg"
      height="400"
      width="2600"
      alt=""
    />
    <h1>下面设计器配置的动态视图</h1>
    <slot name="dynamicView"></slot>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
});
</script>

3: 组件挂载的时候,创建动态视图

// CustomViewWidget.ts

import {
  SPI,
  BaseElementWidget,
  ViewType,
  ViewCache,
  Widget,
  MetadataViewWidget,
  BaseView,
  TableView,
  FormView,
  FormWidget,
  registerLayout,
  DetailView,
  DetailWidget,
  isRelation2MField,
  customQuery,
  FormFieldWidget
} from '@kunlun/dependencies';
import CustomView from './CustomView.vue';
import { delay } from 'lodash-es';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Form,
    widget: 'CustomViewWidget'
  })
)
export class CustomViewWidgetWidget extends BaseElementWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(CustomView);
    return this;
  }

  /**
   * 定义一个属性,用来存储动态视图
   */
  private metadataViewWidget: MetadataViewWidget | undefined;

  /**
   * 获取动态视图的数据
   */
  @Widget.Method()
  private getWidgetData() {
    const children = this.metadataViewWidget?.getChildrenInstance() as BaseView[];
    const child = children[0];

    if (child) {
      return child instanceof TableView ? child.getCurrentDataSource() : child.getCurrentActiveRecords();
    }

    return null;
  }

  /**
   * 触发表单校验
   */
  @Widget.Method()
  private async executeValidate() {
    const children = this.metadataViewWidget?.getChildrenInstance() as BaseView[];
    const child = children[0];
    if (child && child instanceof FormView) {
      const formWidget = child.getChildrenInstance().find((chi) => chi instanceof FormWidget) as FormWidget;

      // 校验
      const rst = await formWidget?.validator();

      return rst;
    }

    return true;
  }

  /**
   * 加载表单视图中字段表格的数据
   */
    private reloadFormX2MFieldData(widget: BaseView, rootData) {
    const formWidget = widget.getChildrenWidget().find((w) => w instanceof FormWidget) as FormWidget;
    if (formWidget) {
      const x2mWidgets = formWidget.getFieldWidgets(true).filter((v) => isRelation2MField(v.field)) as any[];
      for (const widget of x2mWidgets) {
        widget.mountedProcess?.();
        (widget as FormFieldWidget)?.reloadRootData(rootData);
      }
    }
  }

  /**
   * 加载详情视图中字段表格的数据
   */
  private reloadDetailViewData(widget: DetailView, data: any) {
    const detailWidget = widget.getChildrenWidget().find((w) => w instanceof DetailWidget) as DetailWidget;

    if (detailWidget) {
      // 加载详情页数据
      detailWidget.reloadActiveRecords(data);

      // 获取详情页的2M关系字段组件
      const x2mWidgets = detailWidget.getFieldWidgets(true).filter((v) => isRelation2MField(v.field)) as any[];

      for (const widget of x2mWidgets) {
        if (widget.refreshValueProcess) {
          widget.isDataSourceProvider = true;
          widget.refreshValueProcess?.();
        }
      }
    }
  }

  /**
   * 初始化的时候,创建动态视图
   */
  mounted() {
    this.createFormWidget();
  }

  public async createFormWidget() {
    this.load(async () => {
      // 如果视图已经存在,那么先销毁,防止多次创建
      if (this.metadataViewWidget) {
        this.metadataViewWidget.dispose();
        this.metadataViewWidget = undefined;
      }

      // 根据 模型编码 + 视图名称获取设计器配置的视图
      const view = await ViewCache.get(
        'resource.k2.Model0000000100', // 模型编码
        '创建跟编辑_FORM_uiViewf927f2785d3c4394a6c26898df2e8c87' // 视图名称
      );

      if (view) {
        /**
         * 调用 this.createWidget创建对应的视图
         * 第一个参数是代表的是视图对应的widget
         * 第二个参数是创建好的视图需要放在哪个插槽里面,对应vue文件里面的slot
         *   如果vue文件里面写的是 <slot name="dynamicView"></slot>, 那么这个参数就是 'dynamicView'
         *   如果vue文件里面写的是 <slot name='customName'></slot>, 那么这个参数就是 'customName'
         *
         * 第三个参数是视图需要的属性配置
         */

        this.metadataViewWidget = this.createWidget(MetadataViewWidget, 'dynamicView', {
          metadataHandle: this.metadataHandle,
          rootHandle: this.rootHandle,
          internal: true,
          inline: true,
          automatic: true
        });

        // 初始化上下文
        this.metadataViewWidget.initContextByView(view);

        this.forceUpdate();

        // 视图对应的数据源,这里的数据理论上需要通过调用接口获取
        const data = {} as any;
        // const data = await customQuery('resource.k2.Model0000000100', {argumentName: 'query', name: 'queryOne'}, {id: 123}) as any

        delay(async () => {
          const children = this.metadataViewWidget?.getChildrenInstance() as BaseView[];

         if (children.length) {
            const child = children[0]
            // 如果当前视图是表格
            if (child instanceof TableView) {
              await child.refreshCallChaining?.syncCall();
            }  else if (child instanceof DetailView) {  // 如果当前视图是详情
              this.reloadDetailViewData(child, data);
            } else {
                // 如果当前视图是表单
              child.setViewMode(ViewMode.Create);
              child.setCurrentActiveRecords(data);
              this.reloadFormX2MFieldData(child, data);
            }
          }
        }, 100);
      }
    });
  }
}

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

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

(0)
汤乾华的头像汤乾华数式员工
上一篇 2025年3月24日 pm4:33
下一篇 2025年3月25日 am10:55

相关推荐

  • 前端日期组件国际化支持方案

    在 oinone 平台中,系统默认支持基础的国际化翻译功能。但由于日期时间组件的国际化依赖对应语言包,而全量引入语言包会显著增加打包体积,因此前端默认仅集成了中、英文的日期时间支持。若需为日期时间组件扩展其他语言(如日语)的国际化支持,需手动导入对应语言包并完成配置,具体步骤如下: 假设我们现在国际化翻译切换成了日语,那么我们在日期时间也要支持日语,那么需要如下操作: 1: 重写 RootWidget 继承平台默认的 RootWidget,SPI 注册条件保持跟平台一致即可覆盖平台默认的RootWidget // CustomRootWidget.ts import { RootComponentSPI, RootWidget, SPIFactory } from '@oinone/kunlun-dependencies'; import Root from './Root.vue'; // 通过SPI注册覆盖平台默认的root组件 @SPIFactory.Register(RootComponentSPI.Token({ widget: 'root' })) export class CustomRootWidget extends RootWidget { public initialize() { super.initialize(); this.setComponent(Root); return this; } } 2: 覆盖 Root 组件的 Vue 文件 自定义的 Vue 文件需负责导入目标语言(如日语)的语言包,并根据当前语言环境动态切换配置。这里需要同时处理 ant-design-vue、element-plus 组件库及 dayjs 工具的语言包,确保日期组件的展示和交互统一适配目标语言。 <!– Root.vue –> <template> <a-config-provider :locale="antLocale"> <el-config-provider :locale="eleLocale"> <match :rootToken="root"> <template v-for="page in pages" :key="page.widget"> <route v-if="page.widget" :path="page.path" :slotName="page.slotName" :widget="page.widget"> <slot :name="page.slotName" /> </route> </template> <route :path="pagePath" slotName="page" :widgets="{ page: widgets.page }"> <slot name="page" /> </route> <route path="/" slotName="homePage"> <slot name="homePage" /> </route> </match> </el-config-provider> </a-config-provider> </template> <script lang="ts"> import { CurrentLanguage, EN_US_CODE, UrlHelper, ZH_CN_CODE } from '@oinone/kunlun-dependencies'; import { ConfigProvider as AConfigProvider } from 'ant-design-vue'; import { ElConfigProvider } from 'element-plus'; import dayjs from 'dayjs'; // 导入ant-design-vue语言包 import enUS from 'ant-design-vue/es/locale/en_US'; import zhCN from 'ant-design-vue/lib/locale/zh_CN'; import jaJP from 'ant-design-vue/lib/locale/ja_JP'; // 新增:日语语言包 // 导入 dayjs的语言包 import 'dayjs/locale/zh-cn'; import 'dayjs/locale/ja'; // 新增:日语语言包 // 导入element-plus语言包 import elEn from 'element-plus/dist/locale/en.mjs'; import elZhCn from 'element-plus/dist/locale/zh-cn.mjs'; import elJaJP from 'element-plus/dist/locale/ja.mjs'; // 新增:日语语言包 import { computed, defineComponent, onMounted,…

    2025年8月13日
    44800
  • 前端密码加密

    在项目开发中,我们可能会遇到自定义登录页,密码需要加密,或者是数据提交的时候,某个数据需要加密,在平台的前端中,提供了默认的全局加密 API 在 oinone 前端工程使用 // pc端工程使用 import { encrypt } from '@kunlun/dependencies'; // 移动端端工程使用 import { encrypt } from '@kunlun/mobile-dependencies'; // 加密后的密码 const password = encrypt('123456'); 其他工程使用 如果是其他工程,前端没有用到 oinone 这一套,比如小程序,或者是其他工程,可以使用下面的代码记得安装 crypto-js import CryptoJS from 'crypto-js'; const key = CryptoJS.enc.Utf8.parse('1234567890abcdefghijklmnopqrstuv'); const iv = CryptoJS.enc.Utf8.parse('1234567890aabbcc'); export const encrypt = (content: string): string => { if (typeof content === 'string' && content) { const encryptedContent = CryptoJS.AES.encrypt(content, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encryptedContent.ciphertext.toString(); } return ''; };

    2025年3月24日
    41100
  • 左树右表默认选择第一行

    import { BaseElementWidget, Widget, SPI, ViewType, TableSearchTreeWidget } from '@kunlun/dependencies'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'tree', model: '改成当前视图的模型' }) ) export class CustomTableSearchTreeWidget extends TableSearchTreeWidget { protected hasExe = false; @Widget.Watch('rootNode.children.length') protected watchRootNode(len) { if (len && !this.hasExe) { this.hasExe = true; const firstChild = this.rootNode?.children?.[0]; if (firstChild) { this.onNodeSelected(firstChild); this.selectedKeys = [firstChild.key]; } } } }

    2024年11月26日
    1.0K00
  • 「前端」动作API

    概述 在 oinone 前端平台中,提供了四种动作 跳转动作(页面跳转、打开弹窗、抽屉) 服务端动作(调用接口) 客户端动作(返回上一页、关闭弹窗等) 链接动作(打开执行的链接) 快速开始 // 基础使用示例 import { executeViewAction, executeServerAction, executeUrlAction } from '@kunlun/dependencies'; // 示例 1: 基础页面跳转(去创建页面) executeViewAction(action); // 示例 2: 带参数的页面跳转(查询ID为123的数据),去编辑、详情页 executeViewAction(action, undefined, undefined, { id: '123' }); // 示例 3: 页面跳转的参数,用最新的,防止当前页面的参数被带到下一个页面 executeViewAction(action, undefined, undefined, { id: '123' , preserveParameter: true}); // 示例 4: 调用服务端接口 const params = { id: 'xxx', name: 'xxx' }; await executeServerAction(action, params); await executeServerAction(action, params, { maxDepth: 2 }); // 接口数据返回的数据层级是3层 -> 从0开始计算, 默认是2层 // 执行链接动作 executeUrlAction(action); API 详解 executeViewAction 参数名 描述 类型 必填 默认值 — action 视图动作 RuntimeViewAction true router 路由实例 Router false undefined matched 路由匹配参数 Matched false undefined extra 扩展参数 object false {} target 规定在何处打开被链接文档(可参考 a 标签的 target) string false undefined executeServerAction 参数名 描述 类型 必填 默认值 ​action 服务端动作 RuntimeServerAction true param 传递给后端的参数 object true context 配置接口返回的数据层级(默认是两层) {maxDepth: number} false executeUrlAction 参数名 描述 类型 必填 默认值 ​action 链接动作 IURLAction true

    2025年3月21日
    35700
  • 自定义表格支持合并或列、表头分组

    本文将讲解如何通过自定义实现表格支持单元格合并和表头分组。 点击下载对应的代码 在学习该文章之前,你需要先了解: 1: 自定义视图2: 自定义视图、字段只修改 UI,不修改数据和逻辑3: 自定义视图动态渲染界面设计器配置的视图、动作 1. 自定义 widget 创建自定义的 MergeTableWidget,用于支持合并单元格和表头分组。 // MergeTableWidget.ts import { BaseElementWidget, SPI, ViewType, TableWidget, Widget, DslRender } from '@kunlun/dependencies'; import MergeTable from './MergeTable.vue'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Table, widget: 'MergeTableWidget' }) ) export class MergeTableWidget extends TableWidget { public initialize(props) { super.initialize(props); this.setComponent(MergeTable); return this; } /** * 表格展示字段 */ @Widget.Reactive() public get currentModelFields() { return this.metadataRuntimeContext.model.modelFields.filter((f) => !f.invisible); } /** * 渲染行内动作VNode */ @Widget.Method() protected renderRowActionVNodes() { const table = this.metadataRuntimeContext.viewDsl!; const rowAction = table?.widgets.find((w) => w.slot === 'rowActions'); if (rowAction) { return rowAction.widgets.map((w) => DslRender.render(w)); } return null; } } 2. 创建对应的 Vue 组件 定义一个支持合并单元格与表头分组的 Vue 组件。 <!– MergeTable.vue –> <template> <vxe-table border height="500" :column-config="{ resizable: true }" :merge-cells="mergeCells" :data="showDataSource" @checkbox-change="checkboxChange" @checkbox-all="checkedAllChange" > <vxe-column type="checkbox" width="50"></vxe-column> <!– 渲染界面设计器配置的字段 –> <vxe-column v-for="field in currentModelFields" :key="field.name" :field="field.name" :title="field.label" ></vxe-column> <!– 表头分组 https://vxetable.cn/v4.6/#/table/base/group –> <vxe-colgroup title="更多信息"> <vxe-column field="role" title="Role"></vxe-column> <vxe-colgroup title="详细信息"> <vxe-column field="sex" title="Sex"></vxe-column> <vxe-column field="age" title="Age"></vxe-column> </vxe-colgroup> </vxe-colgroup> <vxe-column title="操作" width="120"> <template #default="{ row, $rowIndex }"> <!– 渲染界面设计器配置的行内动作 –> <row-action-render :renderRowActionVNodes="renderRowActionVNodes" :row="row" :rowIndex="$rowIndex" :parentHandle="currentHandle" ></row-action-render> </template> </vxe-column> </vxe-table> <!– 分页 –> <oio-pagination :pageSizeOptions="pageSizeOptions" :currentPage="pagination.current"…

    2025年1月9日
    96900

Leave a Reply

登录后才能评论