自定义组件之手动渲染基础(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低代码应用平台体验

(0)
数式-海波的头像数式-海波数式管理员
上一篇 2023年6月20日 pm4:07
下一篇 2023年11月2日 pm1:58

相关推荐

  • 如何在多标签页切换时自动刷新视图

    在日常项目中,常常会遇到多视图(Multi-View)标签的场景,用户在切换不同视图时,可能需要刷新当前活动标签内的视图数据或状态。本文将详细解析下面这段代码,并说明如何利用它在视图切换时刷新对应的视图。 下列代码写在ss-boot里面的main.ts import { VueOioProvider } from '@kunlun/dependencies'; import { delay } from 'lodash-es'; VueOioProvider( { … 自己的配置 }, [ () => { setTimeout(() => { subscribeRoute( (route) => { const page = route.segmentParams.page || {}; // 如果不是表格类型,则不刷新(根据自己的需求判断) if (page.viewType !== ViewType.Table) { return; } const { model, action } = page; const multiTabsManager = MultiTabsManager.INSTANCE; delay(() => { const tab = multiTabsManager.getActiveTab(); if (tab?.key && tab.stack.some((s) => s.parameters?.model === model && s.parameters?.action === action)) { multiTabsManager.refresh(tab.key); } }, 200); }, { distinct: true } ); }, 1000); } ] ); 1. VueOioProvider 及其作用 首先,代码通过 VueOioProvider 初始化应用程序或组件,并传入两部分参数: 配置对象:可以根据实际业务需求进行自定义配置; 回调函数数组:这里传入了一个匿名函数,用于在应用初始化后执行额外的逻辑 2. 延时执行与路由监听 在回调函数中,使用了 setTimeout 延时 1000 毫秒执行,目的通常是为了确保其他组件或全局状态已经初始化完毕,再开始进行路由监听。 随后,代码调用 subscribeRoute 来监听路由的变化。subscribeRoute 接收两个参数: 回调函数:每次路由变化时都会触发该函数,并将最新的 route 对象传递给它; 配置对象:此处使用 { distinct: true } 来避免重复的触发,提高性能。 3. 判断视图类型 在路由回调函数内部,首先通过 route.segmentParams.page 获取当前页面的配置信息。通过判断 page.viewType 是否等于 ViewType.Table,代码可以确定当前视图是否为“表格类型”: 如果不是表格类型:则直接返回,不做刷新操作; 如果是表格类型:则继续执行后续刷新逻辑。 这种判断机制保证了只有特定类型的视图(例如表格)在切换时才会触发刷新,避免了不必要的操作 4. 多视图标签的刷新逻辑 当确认当前视图为表格类型后,从 MultiTabsManager 中获取当前活动标签: MultiTabsManager.INSTANCE.getActiveTab() 返回当前活动的标签对象; 如果 key 存在,并且激活的标签内部存储的action跟url一致, 就调用 multiTabsManager.refresh(key) 方法来刷新当前标签内的视图。

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

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 用户需要从【创建/编辑】页面中上传多张图片,并且在【详情】页面将这多张图片进行【轮播】展示。 业务分析 从需求来看,我们需要实现一个【轮播图】组件,并且该组件允许在【详情】视图中使用。在其他视图中,我们可以直接使用平台内置的【图片】组件,实现基础的编辑和展示功能。 名词解释 业务模型:需要进行可视化管理的存储模型或代理模型。 准备工作 你需要在某个业务模型下创建一个【表格视图】用于查看全部数据,创建【表单视图】用于创建/编辑数据,并创建【详情视图】展示必要的信息。(为了方便起见,你可以在所有视图中仅使用编码和名称两个字段) 你需要将【表格视图】绑定到某个菜单上,并通过【跳转动作】将三个视图进行关联,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 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日
    3.6K00
  • 如何自定义 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.4K00
  • 「前端」 获取当前用户信息以及语言

    概述 TopBarService 是用于管理用户信息和多语言配置的工具类,提供以下核心功能: 用户信息查询(含缓存机制) 当前语言状态获取 可用语言列表查询 语言切换操作 快速开始 // 基础使用示例 import { TopBarService } from '@kunlun/dependencies'; // 获取用户信息(自动缓存) const userInfo = await TopBarService.getUserInfo(); // 获取当前语言配置 const currentLang = await TopBarService.getCurrentLang(); // 查询支持的语言列表 const languages = await TopBarService.queryLanguageList(); // 激活指定语言 await TopBarService.activeLang('zh_CN'); API 详解 方法功能说明 方法名称 描述 参数 返回值 ​getUserInfo 获取当前用户信息(自动缓存) 无 用户信息对象 ​queryUserInfo 强制获取最新用户信息 无 最新用户信息对象 ​getCurrentLang 获取当前激活的语言配置 无 语言配置对象 / undefined ​queryLanguageList 获取所有可用语言列表 无 语言列表数组 ​activeLang 激活指定语言 语言 ID (string) 更新后的语言配置对象

    2025年3月21日
    82600
  • 移动端端默认布局模板

    默认布局 表格视图(TABLE) <view type="TABLE"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field"> <xslot name="searchFields" slotSupport="field" /> </element> </view> <pack widget="group" class="oio-m-default-view-element" style="height: 100%;flex: 1;overflow-y: hidden;" wrapperStyle="height: 100%;box-sizing:border-box;"> <pack widget="row" style="height: 100%;"> <pack widget="col" mode="full" style="min-height: 234px;height: 100%;"> <element widget="table" slot="table" slotSupport="field" checkbox="false"> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </pack> </pack> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> </view> 表单视图(FORM) <view type="FORM"> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> </view> 详情视图(DETAIL) <view type="DETAIL"> <element widget="detail" slot="detail"> <xslot name="fields" slotSupport="pack,field" /> </element> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> </view> 画廊视图(GALLERY) 默认内嵌布局(inline=true) 内嵌表格视图(TABLE) 内嵌表单视图(FORM) 内嵌详情视图(DETAIL) <view type="DETAIL"> <element widget="detail" slot="detail"> <xslot name="fields" slotSupport="pack,field" /> </element> </view>`

    2024年12月11日
    2.0K00

Leave a Reply

登录后才能评论