自定义组件之手动渲染基础(v4)

阅读之前

你应该:

为什么需要手动渲染

自定义组件之自动渲染(组件插槽的使用)(v4)文章中,我们介绍了带有具名插槽的组件可以使用DSL模板进行自动化渲染,并且可以用相对简单的方式与元数据进行结合。

虽然自动化渲染在实现基本业务逻辑的情况下,有着良好的表现,但自动化渲染方式也有着不可避免的局限性。

比如:当需要多个视图在同一个位置进行切换

在我们的平台中,界面设计器的设计页面,在任何一个组件在选中后,需要渲染对应的右侧属性面板。每个面板的视图信息是保存在对应的元件中的。根据元件的不同,找到对应的视图进行渲染。在单个视图中使用自动化渲染是无法处理这一问题的,我们需要一种可以局部渲染指定视图的方式,来解决这一问题。

获取一个视图

使用ViewCache获取视图
export class ViewCache {
  /**
   * 通过模型编码和名称获取视图
   * @param model 模型编码
   * @param name 名称
   * @param force 强制查询
   * @return 运行时视图
   */
  public static async get(model: string, name: string, force = false): Promise<RuntimeView | undefined>

  /**
   * 通过模型编码、自定义名称和模板获取编译后的视图(此视图非完整视图,仅用于自定义渲染使用)
   * @param model 模型编码
   * @param name 名称(用作缓存key)
   * @param template 视图模板
   * @param force 强制查询
   * @return 运行时视图
   */
  public static async compile(
    model: string,
    name: string,
    template: string,
    force = false
  ): Promise<RuntimeView | undefined>
}
  • ViewCache#get:用于服务端定义视图,客户端直接获取完整视图信息。
  • ViewCache#compile:用于客户端定义视图,通过服务端编译填充元数据相关信息,但不包含视图其他信息。

自定义一个带有具名插槽的组件,并提供切换视图的相关按钮

以下是一个自定义组件的完整示例,其使用ViewCache#compile方法获取视图。

view.ts
const template1 = `<view>
    <field data="id" invisible="true" />
    <field data="code" label="编码" />
    <field data="name" label="名称" />
</view>`;

const template2 = `<view>
    <field data="id" invisible="true" />
    <field data="name" label="名称" />
    <field data="code" label="编码" />
</view>`;

export const templates = {
  template1,
  template2
};
ManualDemoWidget.ts
import {
  BaseElementWidget,
  createRuntimeContextForWidget,
  FormWidget,
  RuntimeView,
  SPI,
  ViewCache,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import ManualDemo from './ManualDemo.vue';
import { templates } 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 resetTemplate(key: string): Promise<void> {
    // 获取运行时视图
    const view = await this.fetchViewByCompile(key);
    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.reloadDataSource(formData);
    this.reloadActiveRecords(formData);

    // 创建所需组件
    this.formWidget = this.createWidget(FormWidget, 'formWidget', {
      metadataHandle: runtimeContextHandle,
      rootHandle: runtimeContextHandle,
      template: runtimeContext.viewTemplate,
      inline: true
    });
  }

  public async fetchViewByCompile(key: string): Promise<RuntimeView | undefined> {
    const template = templates[key];
    if (!template) {
      console.error('error template', key);
      return;
    }

    // 模型编码
    const model = ${model};

    // 通过编译获取视图(非完整视图数据)
    const view = await ViewCache.compile(model, key, template);
    if (!view) {
      return;
    }

    // 补充视图类型
    view.type = ViewType.Form;
    return view;
  }

  protected mounted() {
    // 挂载时初始化指定视图
    this.resetTemplate('template1');
  }
}
ManualDemo.vue
<template>
  <div class="manual-demo-wrapper" v-show="!invisible">
    <oio-button @click="() => switchTemplate('template1')">切换模版1</oio-button>
    <oio-button @click="() => switchTemplate('template2')">切换模版2</oio-button>
    <div class="manual-demo-widget">
      <slot name="formWidget" />
    </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: {
    invisible: {
      type: Boolean,
      default: undefined
    },
    resetTemplate: {
      type: Function
    }
  },
  setup(props) {
    const switchTemplate = (key: string) => {
      props.resetTemplate?.(key);
    };

    return {
      switchTemplate
    };
  }
});
</script>
视图DSL
<view type="FORM">
    <template slot="actionBar">
        <action name="create" label="创建" />
    </template>
    <template slot="form" widget="ManualDemo" />
</view>

在上述示例中,我们在view.ts定义了两个用于切换的视图模板,在视图DSL指定widget="ManualDemo"使用我们定义的组件,以此来实现我们的需求。

关键点详解

Class ComponentVue组件之间的交互

@Widget.Method()
public async resetTemplate(key: string): Promise<void> {
  ......
}
export default defineComponent({
  props: {
    resetTemplate: {
      type: Function
    }
  },
  setup(props) {
    const switchTemplate = (key: string) => {
      props.resetTemplate?.(key);
    };

    return {
      switchTemplate
    };
  }
});

使用@Widget.Method()装饰器,将resetTemplate方法通过props传递给Vue组件,通过Vue组件的用户行为(点击按钮)调用所需的执行逻辑。

在这个示例中,我们通过在setup中定义switchTemplate方法,来避免resetTemplate方法可能为造成的一些异常。在最佳实践中,我们通常不建议开发者直接使用props中定义的Function类型。在大多数情况下,在Class Component中定义的方法应该是尽可能抽象的,而不是为了某个Vue组件提供的特殊方法。

手动渲染常用步骤

  1. 获取运行时视图
  2. 销毁旧组件
  3. 创建运行时上下文
  4. 获取初始化数据
    • 模板中定义的初始化数据
    • 调用服务端接口获取
  5. 根据当前场景选择数据源持有者
    • 提交数据源到当前视图
    • 组件持有数据源
  6. 创建所需组件

一般情况下,我们仅需按照上述步骤就可以实现手动渲染功能。

特别的,我们为了保证组件的生命周期完整,并且在运行时不会出现异常挂载/卸载的情况,自动渲染手动渲染是不允许交叉使用的,并且根据组件所属的场景不同,手动渲染自动渲染更关注数据交互相关的内容。

有时可能会遇到数据交互相关的意外

  • 数据源无法共享导致的动作功能异常
  • 数据源未正确提交到所需组件,可能被某个具有数据源的组件拦截或穿透提交到更上层组件。

获取运行时视图

public async fetchViewByCompile(key: string): Promise<RuntimeView | undefined> {
  // 获取视图模板
  const template = templates[key];
  if (!template) {
    console.error('error template', key);
    return;
  }

  // 模型编码
  const model = ${model};

  // 通过编译获取视图(非完整视图数据)
  const view = await ViewCache.compile(model, key, template);
  if (!view) {
    return;
  }

  // 补充视图类型
  view.type = ViewType.Form;
  return view;
}

在这个示例中,我们通过客户端定义视图模板,传递给服务端进行编译得到运行时视图,并手动补充了视图类型。

但通常情况下,我们采用的是通过服务端定义视图模板,并根据上下文中提供的某些信息,动态获取这个视图的模型编码视图名称,并使用ViewCache#get方法获取的。

如下述方法定义:

public fetchView(model: string, name: string): Promise<RuntimeView | undefined> {
  return ViewCache.get(model, name);
}

创建运行时上下文

const runtimeContext = createRuntimeContextForWidget(view);
const runtimeContextHandle = runtimeContext.handle;
createRuntimeContextForWidget定义
/**
 * 为组件创建运行时上下文,一般用于直接创建组件的场景
 * 注:deep属性必须保持一致
 * @param view 运行时视图
 * @param options 创建可选项 默认值: mergeLayout = false, deep = true
 * @return 组件运行时上下文
 */
export function createRuntimeContextForWidget(
  view: RuntimeView,
  options?: { mergeLayout?: boolean; deep?: boolean }
): RuntimeContext

此处的运行时上下文是针对某个具体组件的模板进行创建的,它与其他通过自动化渲染方式获取的上下文有所不同。

一般的,我们将传入的运行时视图称为组件视图,由此生成的运行时上下文称为组件运行时上下文,将这个运行时上下文中获取的viewTemplate称为组件模板

template1为例:

<view>
    <field data="id" invisible="true" />
    <field data="code" label="编码" />
    <field data="name" label="名称" />
</view>

该模板的view标签并非真实渲染了一个视图组件,而是将其作为组件模板根标签,其标签名称在运行时是没有任何含义的。

对于上述创建的FormWidget组件来说,通过this.getDsl()方法获取的DslDefinition对象就是view标签

例如,我们将template1改为:

<view layout="horizontal">
    <field data="id" invisible="true" />
    <field data="code" label="编码" />
    <field data="name" label="名称" />
</view>

FormWidget组件中,通过this.getDsl().layout将获取到horizontal值。

创建所需组件

// 获取初始化数据
const formData = await runtimeContext.getInitialValue();

// 提交数据源到当前视图
this.reloadDataSource(formData);
this.reloadActiveRecords(formData);

// 创建所需组件
this.formWidget = this.createWidget(FormWidget, 'formWidget', {
  metadataHandle: runtimeContextHandle,
  rootHandle: runtimeContextHandle,
  template: runtimeContext.viewTemplate,
  inline: true
});

在示例中,我们将数据源提交到当前视图用于和其他组件共享数据源,并创建了一个平台提供的FormWidget组件。'formWidget'Vue组件中定义的插槽名称。

创建任何组件时,metadataHandlerootHandle template以及inline属性是必须的。

  • metadataHandlerootHandle:用于应用运行时上下文到当前组件。一般情况下,手动渲染组件的这两个值始终是保持一致的。
  • template:用于指定组件使用的组件模板
  • inline:用于决定当前组件是否与浏览器的URL进行交互。

获取组件模板的初始化数据

  • RuntimeContext#getDefaultValue:收集组件模板field标签定义的defaultValue属性,并返回一个对象
  • RuntimeContext#getInitialValue:在RuntimeContext#getDefaultValue方法的基础上,将field标签定义的relatedcompute属性进行了首次计算,并返回一个对象。(目前最新版本与RuntimeContext#getDefaultValue一致,但我们建议开发者使用该方法)

通常情况下,我们也需要调用服务端服务,来获取最终的初始化数据。在这里不做相关介绍。

根据当前场景选择数据源持有者

数据交互相关章节中,我们对数据源持有者有详细介绍,在这里不再赘述。

视图DSL定义中,我们使用ManualDemo组件的方式决定,我们在这里需要将数据源提交到当前视图,用于actionBar组件中定义的动作进行数据源共享。动作执行时的所需的数据源,就是我们这里提交给当前视图的数据源。

一般情况下,手动渲染组件的数据源不再通过组件的自动查询功能进行数据初始化,这又可能会导致数据交互流程变得难以理解。因此我们建议手动渲染组件的数据源总是在创建组件前就提前准备好的。

下面我们介绍如何让组件持有数据源,该方法并不适用于当前场景,但开发者有必要了解相关内容。

组件持有数据源
// 获取初始化数据
const formData = await runtimeContext.getInitialValue();

// 创建所需组件
this.formWidget = this.createWidget(FormWidget, 'formWidget', {
  metadataHandle: runtimeContextHandle,
  rootHandle: runtimeContextHandle,
  dataSource: formData,
  activeRecords: formData,
  template: runtimeContext.viewTemplate,
  inline: true
});

与之前不同的是,我们将dataSourceactiveRecords通过属性直接传递给组件实例即可。

在当前场景中,这样修改后的结果将导致页面中的按钮无法获取数据源,无法点击。

支持手动渲染的组件

在平台提供的所有组件中,并非所有组件都支持手动渲染。包括但不限于如下几个组件:

  • TableWidget:表格组件
  • FormWidget:表单组件
  • DetailWidget:表单组件(详情)

如何让组件支持手动渲染

支持手动渲染的组件与普通组件唯一的不同就是对metadataHandlerootHandle的处理。

Vue框架下,我们提供了mixin混入的方式,让开发者可以更简单的定义一个支持手动渲染的组件。

组件注册、组件定义都无需改变,仅需在对应的Vue组件中添加ManualWidget混入即可。如下所示:

<script lang="ts">
import { ManualWidget } from '@kunlun/dependencies';
import { defineComponent } from 'vue';

export default defineComponent({
  ......
  mixins: [ManualWidget],
  ......
});
</script>

Oinone社区 作者:数式-海波原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/32.html

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

Like (0)
数式-海波's avatar数式-海波数式管理员
Previous 2023年6月20日 pm4:07
Next 2023年11月2日 pm1:58

相关推荐

  • 如何提高自定义组件的开发效率

    引言 本文将通过前端的开发者模式带领大家提高自定义组件的开发效率 支持2024年9月6日之后用npm i安装的4.7.x之后的所有版本 介绍前端开发者模式 开发者模式的特性 浏览器控制台可以看到更多利于开发的日志输出 页面顶部状态栏消息模块的轮询接口,将只在页面刷新后请求一次,这样会减少开发阶段不必要的请求,以及解决后端断点调试的时候被消息轮询干扰的问题 页面视图的数据将不在走缓存,而是每次实时从base_view表里查询template字段获取,方便需要实时看通过xml修改的页面效果 如何进入开发者模式 通过在浏览器地址栏后追加;mode=dev刷新页面进入开发者模式,页面加载后可以在浏览器开发者工具的应用标签下的本地存储空间(即localStorage)中看到本标识的存储,如果想退出开发者模式可以手动清除该值 开发者模式的应用场景 1.通过控制台跳转到视图动作(ViewAction)的调试页面 页面刷新后,可以在浏览器开发者工具的控制台看到如下图的日志输出,直接点击该链接可以跳转到当前页面的调试链接弹窗视图动作、抽屉视图动作等以弹出层为展示效果的页面,之前的版本是无法进入调试链接查看的,现在可以在弹窗等视图动作点击后在控制台看到该链接点击进入调试页面 2.查看页面母版(mask)、布局(layout)的匹配情况 母版(mask)匹配结果布局(layout)匹配结果 3.查看视图组件(View)、字段组件(Field)、动作组件(Action)的匹配情况 通过浏览器开发者工具的控制台,我们可以看到每个视图、字段、字段匹配到的组件的class类名,这样我们就知道当前用的是那个组件,在自定义的时候可以在源码查看该组件类的实现代码,然后再选择是否继承该父类。另外我们在给组件定义SPI条件的时候,可以参考匹配入参和匹配结果内的属性。 匹配入参是当前元数据SPI的最全量注册条件 匹配结果是当前匹配到的组件的注册条件,一般情况下我们只需要在匹配结果的条件加上当前模型编码(model)、视图名称(viewName)、字段名称(name)、动作名称(actionName)等条件就可以完成自定义组件的覆盖。 在浏览器开发者工具的控制台,通过模型编码、视图名称、字段名称、动作名称等关键字快速搜索定位到需要查找的组件 控制台搜索快捷键:win系统通过ctrl+f,mac系统通过cmd+f 总结 在确保自定义部分代码最终正确被import导入到启动工程main.ts内的情况下,可以通过开发者模式在浏览器控制台打印的日志来排查组件未正确覆盖的问题。

    2024年9月6日
    2.1K00
  • 前端6.2.x升级指南

    升级背景 从 6.2.x 版本开始,平台底层核心代码已全面开源(设计器相关代码除外),本次升级涉及包名变更和配置调整,以下是详细的升级步骤 1. 更新 .npmrc 配置 修改当前项目根目录 .npmrc 文件,内容如下: registry=https://registry.npmmirror.com/ @oinone:registry=http://nexus.shushi.pro/repository/kunlun/ @kunlun:registry=http://nexus.shushi.pro/repository/kunlun/ shamefully-hoist=true legacy-peer-deps=true link-workspace-packages = true prefer-workspace-packages = true recursive-install = true lockfile=true 2. 包名变更 以前的包是 @kunlun/xxxx,现在的包名是@oinone/kunlun开头,所以需要全部替换修改。 旧包名 新包名 @kunlun/dependencies @oinone/kunlun-dependencies @kunlun/vue-ui-antd @oinone/kunlun-vue-ui-antd 3. 升级依赖版本 修改所有 package.json 中更新所有平台相关依赖: – "@kunlun/dependencies": "~5.7.0" + "@oinone/kunlun-dependencies": "~6.2.0" boot工程的package.json新增devDependencies依赖 "devDependencies": { "@types/lodash": "4.14.182", "@types/lodash-es": "4.17.6", ….. } 4. 更新代码引用 全局搜索并替换代码中的所有 @kunlun/ 为 @oinone/kunlun- 完整的 main.ts import 'ant-design-vue/dist/antd.min.css'; import 'element-plus/dist/index.css'; import '@oinone/kunlun-vue-ui-antd/dist/oinone-kunlun-vue-ui-antd.css'; import '@oinone/kunlun-vue-ui-el/dist/oinone-kunlun-vue-ui-el.css'; import '@oinone/kunlun-designer-common/dist/oinone-kunlun-designer-common.css'; import '@oinone/kunlun-model-designer/dist/oinone-kunlun-model-designer.css'; import '@oinone/kunlun-workflow-designer/dist/oinone-kunlun-workflow-designer.css'; import '@oinone/kunlun-workflow/dist/oinone-kunlun-workflow.css'; import '@oinone/kunlun-data-designer/dist/oinone-kunlun-data-designer.css'; import '@oinone/kunlun-microflow-designer/dist/oinone-kunlun-microflow-designer.css'; import 'reflect-metadata'; import ElementPlus from 'element-plus'; import { App } from 'vue'; import { VueOioProvider, RuntimeContextManager } from '@oinone/kunlun-dependencies'; // 设计器 import '@oinone/kunlun-ui-designer-dependencies'; import '@oinone/kunlun-print-designer-dependencies'; import '@oinone/kunlun-workflow'; import '@oinone/kunlun-workflow-designer'; import '@oinone/kunlun-model-designer'; import '@oinone/kunlun-data-designer'; import '@oinone/kunlun-data-designer-open-pc'; import '@oinone/kunlun-eip-dependencies'; import '@oinone/kunlun-ai-dependencies'; import '@oinone/kunlun-microflow-designer'; import '@oinone/kunlun-designer-common-ee'; // 下面三个依赖是当前工程的包,请根据实际场景修改 import '@ss/oinone'; import '@ss/admin-widget'; import '@ss/project'; VueOioProvider( { browser: { title: 'Oinone – 构你想象!', favicon: 'https://pamirs.oss-cn-hangzhou.aliyuncs.com/pamirs/image/default_favicon.ico' } }, [ () => { /// 获取当前vue实例 const app = RuntimeContextManager.createOrReplace().frameworkInstance as App; } ] ); 当配置全部修改完成后,重写安装依赖 # 清理旧依赖 pnpm clean # 安装新依赖 pnpm install

    2025年6月17日
    80200
  • TableWidget 与 FormWidget 浅析

    前言:本文主要聚集于 TableWidget和 FormWidget,对两者以外的内容不做赘述。 TableWidget和 FormWidget作为一个基本的渲染模块与 Mask等不同,TableWidget与 FormWidget有着明确的目的,比如 TableWidget就是作为一个表格视图,这种名称中可以看得出来。其将表格的一系列能力聚拢,如单元格,行列选择排序,翻页等等; TableWidget 我们使用表格到底是在使用什么? 在我们讲述渲染流程前,我们需要提一个问题,在我们使用表格组件时,我们在使用什么,换而言之,我们对表格组件中关注的是什么?应该说我们更关注的是表格所展示的数据。TableWidget或者说整个低代码其实解决的就是这个问题,让我们可以很方便的展示数据。不用关心一些细枝末节 TableWidget 的渲染 TableWidget在整个渲染流程中的负责组装各种 Vue 组件所需要的核心数据或事件回调并传递给其绑定的 Vue 组件即 DefaultTable,如 allowRowClick,rowClickMode等等,这些值会作为 Props 传递给 DefaultTable,DefaultTable则一定意义上充当了一个桥接层,主要做了两件事,处理 Props, 处理 Slot,TableWidget传递给 DefaultTable的 Props 会经过 DefaultTable再次组合,创建新的 Props,同时根据当前的 Props 判断是否有必要新增一些 slot,比如 OioPagination组件是否需要渲染就取决于 Props.showPagination 的值,经过 DefaultTable的处理后, props与 slot会进一步交给 OioTable进行渲染,OioTable则会进一步聚合处理,比如如果没有 defaultSlot则进行空值的渲染,如果存在 footSlot则会构建一个包裹层去包裹它。这些组件协同完成了一个表格的结构,而我们真正关心的数据则由一个个 BaseTableColumnWidget渲染。BaseTableColumnWidget的渲染过程类似于 TableWidget,其负责组装 DefaultTableColumn渲染所需要的 props,然后交给 OioTableColumn进行实际渲染。并且会有多种基于 BaseTableColumnWidget的 widget通过重写 renderDefaultSlot,renderEditSlot,renderContentSlot,renderHeaderSlot等几个 props 实现不同状态,不同类型等组件的渲染。通过 TableWidget与 BaseTableColumnWidget相结合, Table 页面完成了主体框架与内容的渲染。而当数据完成渲染后,不可避免的会有数据交互,比如分页,排序,过滤等,这些交互都由 TableWidget与 BaseTableColumnWidget共同完成。比如排序翻页等,TableWidget会将事件作为 props 向下注入,当事件被调用,TableWidget会进行处理,比如发起请求,或者对内部数据排序等。而除了数据的展示,还有一些动作,即 Action ,Action 被触发时会按照内部的配置进行工作,比如编辑时,将获取 activeRecord,随后触发 Table 的编辑。 TableWidget就是这样去渲染出了一个完整的表格页面。能够完成数据的增删改查等操作 FormWidget FormWidget与 TableWidget一样,也是作为一个渲染模块,但是它与 TableWidget不同的是,FormWidget是作为一个表单视图,其将表单的一系列能力聚拢,如表单提交,表单校验,表单重置等等。其重点在于对数据的增改。所以在提供的能力上也有区别,比如 FormWidget没有提供翻页,排序,过滤等能力,因为这些能力属于表格的能力,而 FormWidget则更关注于表单的能力。提供了数据的存储,提交,校验等能力 Form 在渲染时流程与 Table 大同小异,其同样为三层结构 FormWidget => DefaultForm => FormFieldWidget 一层层向下渲染,不同的在于 FormWidget 更多的关注点在于维护其内部的 FormData 这是整个表单页面所围绕的东西,当页面上的控件发生变化,其变更的值会被收集到 FormData 中,并在后续中使用。同时在编辑已有数据场景下,Form 会将数据加载到 FormData,随后下放给 FormFiledWidget。 异同之处 从介绍中可以看出,Table 侧重于数据的查询展示,Form 则侧重于数据的变动处理,但是抽象的看其核心其实是同一套逻辑,即数据的存储与展示,中间或许会有对数据的某些处理,但是并不是本质上的区别,两者在核心理念上是一致的,即让使用者只需要关心数据本身,而不需要关注于繁琐的视图构造,这是整个低代码甚至前端的发展方向。

    2025年3月25日
    1.1K00
  • 如何在Oinone 根据主题实现自定义组件样式

    在页面交互中,样式的变化是前端核心工作之一。本文介绍如何在Oinone平台中根据主题变化自定义组件样式。 介绍 Oinone平台提供了六种不同的主题设置,浅色大主题、浅色中主题、浅色小主题、深色大主题、深色中主题、深色小主题,默认采用浅色中主题。本文旨在指导如何在线或通过代码修改这些主题,以满足个性化需求。 基础知识 Oinone平台的默认主题为浅色中主题,用户可以根据喜好选择以下六种主题中的任何一种: 浅色大主题 浅色中主题 浅色小主题 深色大主题 深色中主题 深色小主题 在线修改主题 用户可以通过进入系统配置应用,并切换到系统风格配置菜单来在线修改主题。选择喜欢的主题并保存即可轻松更换。 代码修改主题 步骤示例 新建theme.ts文件 在项目的src目录下新建一个theme.ts文件。 定义主题变量 在theme.ts文件中定义主题名称和CSS变量,示例中将主色系替换为黑色。 export const themeName = ‘OinoneTheme’; export const themeCssVars = { ‘primary-color’: ‘black’, ‘primary-color-hover’: ‘black’, ‘primary-color-rgb’: ‘0, 0, 0’, ‘primary-color-focus’: ‘black’, ‘primary-color-active’: ‘black’, ‘primary-color-outline’: ‘black’, }; 在main.ts注册 import { registerTheme, VueOioProvider } from ‘@kunlun/dependencies’; // 引入注册主题组件 import { themeName, themeCssVars } from ‘./theme’; // 引入theme.ts registerTheme(themeName, themeCssVars);// 注册 VueOioProvider( { …other config theme: [themeName] // 定义的themeName传入provider中 }, [] ); 4: 刷新页面看效果 注意事项 确保在定义CSS变量时遵循主题设计规范。 正确引入theme.ts文件以避免编译错误。 总结 本文详细介绍了在Oinone平台中修改主题的两种方法:在线修改和代码修改。这些步骤允许开发者和用户根据个人喜好或项目需求,自定义界面的主题风格。

    2024年2月26日
    1.8K00
  • 自定义视图部分区域渲染设计器的配置

    自定义视图与界面设计器配置对接 在日常开发中,我们经常会遇到自定义视图的需求。自定义视图不仅需要与平台机制结合,还要实现与界面设计器中配置的字段和动作的无缝对接。本文将介绍如何将自定义视图与界面设计器中配置的字段和动作的无缝对接,实现字段和动作的渲染。 用大白话来讲就是:当前页面一部分是自定义的,一部分是设计器生成的 代码地址 目录 自定义表单视图与字段、动作的结合 自定义表格视图与字段、动作的结合 自定义表单视图与字段、动作的结合 首先需要在界面设计器配置好对应界面,虽然配置的页面样式跟期望展示的 UI 的不一样,但是数据的分发、汇总以及动作的交互也是一致的,所以我们可以通过自定义的方式替换这个页面的 UI,但是数据以及动作,是完全可以通过平台的能力获取的。 注册表单对应的 SPI // CustomFormWidget.ts import CustomForm from './CustomForm.vue'; @SPI.ClassFactory( BaseElementWidget.Token({ viewType: ViewType.Form, widget: 'CustomFormWidget' }) ) export class CustomFormWidget extends FormWidget { public initialize(props: BaseElementObjectViewWidgetProps): this { super.initialize(props); this.setComponent(CustomForm); return this; } } <!– CustomForm.vue –> <template> <div class="custom-form-container"> <div class="custom-form-tip">自定义视图</div> </div> </template> <script lang="ts"> import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies'; import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue'; export default defineComponent({ inheritAttrs: false, props: { formData: { type: Object as PropType<Record<string, any>>, default: () => ({}) } } }); </script> 在上述的代码中,我们继承的是 FormWidget,那么这个页面会自动发起对应的请求,所有的数据都在 formData 中。 表单视图渲染动作 在最开始我们讲到过,当前页面是在界面设计器配置过,所有在CustomFormWidget里面是可以拿到当前页面配置的元数据信息,所以我们可以拿到界面设计器配置的字段跟动作 /// CustomFormWidget.ts @Widget.Method() protected renderActionVNodes() { // 从dsl中获取actionBar,actionBar里面包含了界面设计器配置的动作 const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actionBar'); if (actionBar) { // actionBar.widgets 就是界面设计器配置的动作,借助平台提供的DslRender.render方法,将对应的dsl转换成VNode return actionBar.widgets.map((w, index) => DslRender.render({ …w, __index: index }) ); } return null; } 因为 renderActionVNodes 方法返回的是 VNode,所以我们必须借助 vue 的 render 函数才能渲染。 <!– ActionRender.vue –> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ inheritAttrs: false, props: { renderActionVNodes: { type: Function, required: true } }, render() { return this.renderActionVNodes(); } });…

    2024年9月12日
    1.8K01

Leave a Reply

Please Login to Comment