自定义视图部分区域渲染设计器的配置

自定义视图与界面设计器配置对接

在日常开发中,我们经常会遇到自定义视图的需求。自定义视图不仅需要与平台机制结合,还要实现与界面设计器中配置的字段和动作的无缝对接。本文将介绍如何将自定义视图与界面设计器中配置的字段和动作的无缝对接,实现字段和动作的渲染。

用大白话来讲就是:当前页面一部分是自定义的,一部分是设计器生成的

代码地址

目录

  1. 自定义表单视图与字段、动作的结合
  2. 自定义表格视图与字段、动作的结合

自定义表单视图与字段、动作的结合

首先需要在界面设计器配置好对应界面,虽然配置的页面样式跟期望展示的 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();
    }
  });
</script>

在ActionRender.vue中,通过 props 接收renderActionVNodes 方法,最终在 render 函数中执行,所以只需要在CustomForm.vue导入ActionRender.vue。

<template>
  <div class="custom-form-container">
    <div class="custom-form-tip">自定义视图</div>

    <p style="color: red">下面是通过平台机制生成的动作</p>

    <div style="display: flex; gap: 10px">
      <action-render :renderActionVNodes="renderActionVNodes"></action-render>
    </div>
  </div>
</template>
<script lang="ts">
  import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies';
  import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue';
  import ActionRender from './components/ActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderActionVNodes: {
        type: Function,
        required: true
      }
    },
    components: {
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-form-container {
    .custom-form-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

表单视图渲染字段

渲染界面设计器配置的字段和刚刚讲到的动作是一致的。

// CustomFormWidget.ts
@Widget.Method()
  protected renderFieldVNodes() {
    // 获取界面设计器配置的字段
    const modelFields = this.metadataRuntimeContext.model.modelFields;

    if (modelFields.length) {
      //借助平台提供的DslRender.render方法,将对应的dsl转换成VNode
      return modelFields.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

因为renderFieldVNodes 返回的也是 VNode,所以我们还是需要借助 vue 的 render 函数来渲染.

<!-- FieldRender.vue -->
<script lang="ts">
  import { defineComponent } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderFieldVNodes: {
        type: Function,
        required: true
      }
    },
    render() {
      return this.renderFieldVNodes();
    }
  });
</script>

最终也是在CustomForm.vue中导入FieldRender.vue。

下面是完整的代码:

// CustomFormWidget.ts

import {
  BaseElementObjectViewWidgetProps,
  BaseElementWidget,
  DslRender,
  FORM_WIDGET,
  FormWidget,
  SPI,
  ViewType,
  Widget
} from '@kunlun/dependencies';

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;
  }

  /**
   * 渲染字段VNode
   */
  @Widget.Method()
  protected renderFieldVNodes() {
    const modelFields = this.metadataRuntimeContext.model.modelFields;

    if (modelFields.length) {
      return modelFields.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

  /**
   * 渲染动作VNode
   */
  @Widget.Method()
  protected renderActionVNodes() {
    const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actionBar');

    if (actionBar) {
      return actionBar.widgets.map((w, index) =>
        DslRender.render({
          ...w,
          __index: index
        })
      );
    }

    return null;
  }

  @Widget.Reactive()
  protected showFieldVNode = false;

  protected async mountedProcess() {
    await super.mountedProcess();

    this.showFieldVNode = true;
  }
}
<!-- CustomForm.vue -->
<template>
  <div class="custom-form-container">
    <div class="custom-form-tip">自定义视图</div>

    <p style="color: red">下面是通过平台机制生成的字段</p>

    <field-render v-if="showFieldVNode" :renderFieldVNodes="renderFieldVNodes"></field-render>

    <p style="color: red">下面是通过平台机制生成的动作</p>

    <div style="display: flex; gap: 10px"><action-render :renderActionVNodes="renderActionVNodes"></action-render></div>
  </div>
</template>
<script lang="ts">
  import { DslRender, DslRenderDefinition, PropRecordHelper } from '@kunlun/dependencies';
  import { createVNode, defineComponent, onMounted, PropType, ref, VNode } from 'vue';
  import FieldRender from './components/FieldRender.vue';
  import ActionRender from './components/ActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      renderActionVNodes: {
        type: Function,
        required: true
      },
      renderFieldVNodes: {
        type: Function,
        required: true
      },
    showFieldVNode: {
      type: Boolean,
      default: false
    }
    },
    components: {
      FieldRender,
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-form-container {
    .custom-form-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

注册 layout

registerLayout(
  `<view type="FORM">
    <element widget="CustomFormWidget" slot="form">
        <xslot name="fields" slotSupport="pack,field" />
    </element>
  </view>`,
  {
    moduleName: 'moduleName',
    model: 'model',
    viewType: ViewType.Form,
    actionName: 'actionName'
  }
);

自定义表格视图与字段动作的结合

注册表格对应的 SPI

// CustomTableWidget.ts

import {
  BaseElementObjectViewWidgetProps,
  BaseElementWidget,
  DslRender,
  SPI,
  TableWidget,
  ViewType,
  Widget
} from '@kunlun/dependencies';
import CustomTable from './CustomTable.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Table,
    widget: 'CustomTableWidget'
  })
)
export class CustomTableWidget extends TableWidget {
  public initialize(props: BaseElementObjectViewWidgetProps): this {
    super.initialize(props);
    this.setComponent(CustomTable);
    return this;
  }

  /**
   * 渲染行内动作VNode
   */
  @Widget.Method()
  protected renderRowActionVNodes() {
   // const table = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'table');

    // const rowAction = table?.widgets.find((w) => w.slot === 'rowActions');
    // this.refreshCallChaining;
    // if (rowAction) {
    //   return rowAction.widgets.map((w) => DslRender.render(w));
    // }

    // return null;

    const modelActions = this.metadataRuntimeContext.model.modelActions;

    if (modelActions.length) {
      return modelActions.map((w) => DslRender.render(w.template!));
    }

    return null;
  }

  /**
   * 渲染动作VNode
   */
  @Widget.Method()
  protected renderActionVNodes() {
    const actionBar = this.metadataRuntimeContext.viewDsl?.widgets.find((w) => w.slot === 'actions');

    if (actionBar) {
      return actionBar.widgets.map((w, index) =>
        DslRender.render({
          ...w,
          __index: index
        })
      );
    }

    return null;
  }
}

在上述代码中,我们继承的是TableWidget,所以页面也会自动发起查询,并且定义了renderRowActionVNodes和renderActionVNodes方法,用于渲染行内动作和动作。

表格视图渲染动作

<!-- CustomTable.vue -->
<template>
  <div class="custom-table-container">
    <div class="custom-table-tip">自定义视图</div>

    <div style="display: flex; gap: 10px">
      <p style="color: red; margin-top: 10px">这是表格的动作</p>
      <action-render :renderActionVNodes="renderActionVNodes"></action-render>
    </div>

    <p style="color: red; margin-top: 10px">这是表格的数据跟行内动作</p>
    <div>
      <div v-for="(row, index) in dataSource" :key="row.index">
        <div style="display: flex">
          <div><span>行数据</span> <span> ID:{{ row.id }}</span></div>
          <div>
            <span>行动作</span>
            <span>
              <row-action-render
                :renderRowActionVNodes="renderRowActionVNodes"
                :row="row"
                :rowIndex="index"
                :parentHandle="currentHandle"
              ></row-action-render>
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
  import { defineComponent, PropType } from 'vue';
  import ActionRender from './components/ActionRender.vue';
  import RowActionRender from './components/RowActionRender.vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
        currentHandle: {
          type: String,
          required: true
        },
      renderActionVNodes: {
        type: Function,
        required: true
      },
      renderRowActionVNodes: {
        type: Function,
        required: true
      },
      dataSource: {
        type: Array as PropType<any[]>,
        default: () => []
      }
    },
    components: {
      RowActionRender,
      ActionRender
    }
  });
</script>
<style lang="scss">
  .custom-table-container {
    .custom-table-tip {
      color: var(--oio-primary-color);
      margin-bottom: var(--oio-margin);
      font-size: 24px;
    }
  }
</style>

ActionRender.vue跟表单中的ActionRender.vue一样,内部也是在 render 函数中调用renderActionVNodes。

RowActionRender.vue有点特殊,下面是对应的代码(固定写法)。

<script lang="ts">
  import { ActionBar, RowActionBarWidget, uniqueKeyGenerator } from '@kunlun/dependencies';
  import { debounce } from 'lodash-es';
  import { createVNode, defineComponent } from 'vue';

  export default defineComponent({
    inheritAttrs: false,
    props: {
      row: {
        type: Object,
        required: true
      },
      rowIndex: {
        type: Number,
        required: true
      },
      renderRowActionVNodes: {
        type: Function,
        required: true
      },
      parentHandle: {
          type: String,
          required: true
        }
    },
    render() {
      const vnode = this.renderRowActionVNodes();

      return createVNode(
        ActionBar,
        {
          widget: 'rowAction',
          parentHandle: this.parentHandle,
          inline: true,
          activeRecords: this.row,
          rowIndex: this.rowIndex,
          key: this.rowIndex,
          refreshWidgetRecord: debounce((widget?: RowActionBarWidget) => {
            if (widget) {
              widget.setCurrentActiveRecords(this.row);
            }
          })
        },
        {
          default: () => vnode
        }
      );
    }
  });
</script>

注册 layout

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

registerLayout(
  `<view type="TABLE">
    <element widget="CustomTableWidget" slot="table" slotSupport="field">
            <element widget="expandColumn" slot="expandRow" />
            <xslot name="fields" slotSupport="field" />
            <element widget="rowActions" slot="rowActions" slotSupport="action" />
        </element>
</view>`,
  {
    moduleName: 'resource',
    model: 'resource.ResourceProvince',
    viewType: ViewType.Table
  }
);

本文讲解了自定义视图与界面设计器中配置的字段和动作的无缝对接,以实现自定义视图的功能,本质上是将界面设计器配置的字段、动作,渲染成对应的 VNode,然后在自定义的页面渲染。

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

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

Like (1)
汤乾华's avatar汤乾华数式员工
Previous 2024年9月12日 am11:23
Next 2024年9月12日 pm10:16

相关推荐

  • 组件SPI机制(v4)

    阅读之前 你应该: 了解DSL相关内容。母版-布局-DSL 渲染基础(v4) 组件SPI简介 不论是母版、布局还是DSL,所有定义在模板中的标签都是通过组件SPI机制获取到对应Class Component(ts)并继续执行渲染逻辑。 基本概念: 标签:xml中的标签,json中的dslNodeType属性。 Token组件:用于收集一组Class Component(ts)的基础组件。通常该基础组件包含了对应的一组基础能力(属性、函数等) 维度(dsl属性):用于从Token组件收集的所有Class Component(ts)组件中查找最佳匹配的参数。 组件SPI机制将通过指定维度按照有权重的最长路径匹配算法获取最佳匹配的组件。 组件注册到指定Token组件 以BaseFieldWidget这个SPIToken组件为例,可以用如下方式,注册一个可以被field标签处理的自定义组件: (以下示例仅仅为了体现SPI注册的维度,而并非实际业务中使用的组件代码) 注册一个String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String }) ) export class FormStringFieldWidget extends BaseFieldWidget { …… } 注册一个多值的String类型组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 是否多值:是 说明: 该字段组件可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且该字段为多值字段 @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, multi: true }) ) export class FormStringMultiFieldWidget extends BaseFieldWidget { …… } 注册一个String类型的Hyperlinks组件 维度: 视图类型:表单(FORM) 字段业务类型:String类型 组件名称:Hyperlinks 说明: 该字段组件仅可以在表单(FORM)视图中使用 并且该字段的业务类型是String类型 并且组件名称必须指定为Hyperlinks @SPI.ClassFactory( BaseFieldWidget.Token({ viewType: ViewType.Form, ttype: ModelFieldType.String, widget: 'Hyperlinks' }) ) export class FormStringHyperlinksFieldWidget extends BaseFieldWidget { …… } 当上述组件全部按顺序注册在BaseFieldWidget这个SPIToken组件中时,将形成一个以BaseFieldWidget为根节点的树: “` mermaidgraph TDBaseFieldWidget —> FormStringFieldWidgetBaseFieldWidget —> FormStringMultiFieldWidgetFormStringFieldWidget —> FormStringHyperlinksFieldWidget“` 树的构建 上述形成的组件树实际并非真实的存储结构,真实的存储结构是通过维度进行存储的,如下图所示: (圆角矩形表示维度上的属性和值,矩形表示对应的组件) “` mermaidgraph TDviewType([viewType: ViewType.Form]) —>ttype([ttype: ModelFieldType.Strng]) —>multi([multi: true]) & widget([widget: &#039;Hyperlinks&#039;]) direction LRttype —> FormStringFieldWidgetmulti —> FormStringMultiFieldWidgetwidget —> FormStringHyperlinksFieldWidget“` 有权重的最长路径匹配 同样以上述BaseFieldWidget组件为例,该组件可用的维度有: viewType:ViewType[Enum] ttype:ModelFieldType[Enum] multi:[Boolean] widget:[String] model:[String] viewName:[String] name:[String] 当field标签被渲染时,我们会组装一个描述当前获取维度的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget": "", // 在dsl中定义的任意值 "model": "", // 在dsl编译后自动填充 "viewName": "", // 当前视图名称 "name": "" // 字段的name属性,在dsl编译后自动填充 } 当我们需要使用FormStringHyperlinksFieldWidget这个组件时,我们在dsl中会使用如下方式定义: <view type="FORM" title="演示表单" name="演示模型form" model="demo.DemoModel"> <field data="name" widget="Hyperlinks" /> </view> 此时,我们虽然没有在dsl中定义维度中的其他信息,但在dsl返回到前端时,经过了后端编译填充了对应元数据相关属性,我们可以得到如下所示的对象: { "viewType": "FORM", "ttype": "STRING", "multi": false, "widget":…

    2023年11月1日
    1.4K00
  • 表单字段API

    FormFieldWidget 表单字段的基类,包含了表单字段通用的属性跟方法 示例 class MyFieldClass extends FormFieldWidget{ } 字段属性 属性名 说明 类型 可选值 默认值 value 当前字段的值 any – null formData 当前表单视图的数据 Object – {} rootData 跟视图的数据,如果当前只有一个视图,那么与formData是一样的 Array – [] metadataRuntimeContext 当前视图运行时的上下文,可以获取当前模型、字段、动作、视图等所有的数据 Object – – urlParameters 当前url参数 Object – – field 当前字段的元数据 Object – – model 当前模型 Object – – view 当前视图 Object – – disabled 是否禁用 Boolean – false invisible 当前字段是否不可见 Boolean – false required 当前字段是否必填,如果当前字段是在详情页,那么是false Boolean – false readonly 当前字段是否只读,如果当前字段是在详情页、搜索,那么是false Boolean – false placeholder 占位符 String – 当前字段的displayName label 字段的标题 String – 当前字段的displayName 方法 方法名 说明 参数 例子 getDsl 获取当前字段所有的配置 – change 修改当前字段的值 any focus 获取焦点触发的方法 – blur 失去焦点触发的方法 – executeValidator 执行当前字段的校验,异步的 – submit 重写当前字段的提交逻辑 – submit() { return ‘value’ } reloadActiveRecords 替换当前视图的数据 Array this.reloadActiveRecords([{code: xxx, name: 111}]) reloadRootData 替换根视图的数据 Array this.reloadRootData([{code: xxx, name: 111}])

    2023年11月15日
    2.5K00
  • 前端-如何修改指定页面的内组件的css样式

    为组件加自定义class,用该class作为父选择器写特定的css样式 以form为例,自定义了以下class <view/>标签的表单视图(FormView)组件 <element/>标签的form(FormWidget)组件 <element/>标签的actionBar(ActionBarWidget)组件 import { registerLayout, ViewType } from '@kunlun/dependencies'; export const install = () => { registerLayout( ` <view type="FORM" class="my-form-view"> <element widget="form" slot="form" class="my-form-widget"> <xslot name="fields" slotSupport="pack,field" /> </element> <element widget="actionBar" slot="actionBar" class="my-action-bar" slotSupport="action" > <xslot name="actions" slotSupport="action" /> </element> </view> `, { viewType: ViewType.Form, model: 'resource.k2.Model0000000109', actionName: 'uiViewb2de116be1754ff781e1ffa8065477fa' } ); }; install(); 查看修改后的页面html结构 编写样式的css .my-form-view .oio-form { /** TODO **/ } .my-form-widget .oio-row { /** TODO **/ } .my-action-bar .oio-col { /** TODO **/ }

    2024年6月17日
    2.2K00
  • 如何在多标签页切换时自动刷新视图

    在日常项目中,常常会遇到多视图(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日
    1.7K00
  • 【前端】项目开发前端知识要点地图

    概述 下面整理了目前现有的所有文章,并提供了基本的学习路径。所有使用*标记的文章属于推荐必读文章。 目录 基础篇 【路由】浏览器地址栏url参数介绍 母版-布局-DSL 渲染基础(v4)* 组件SPI机制(v4)* 组件数据交互基础(v4)* 组件生命周期(v4) 入门篇 自定义视图组件(v4)* 如何通过浏览器开发者工具提高调试效率* 如何提高自定义组件的开发效率* 自定义组件之自动渲染(组件插槽的使用)(v4)* GraphQL请求详解(v4)* 上下文在字段和动作中的应用 如何实现页面间的跳转 如何自定义指定页面的样式 进阶篇 自定义组件之手动渲染基础(v4) 自定义组件之手动渲染弹出层(v4) 自定义组件之手动渲染任意视图(v4) 【前端】IOC容器(v4) 最佳实践篇 【前端】工程结构最佳实践(v4)* 【前端】移动端工程结构最佳实践(v4)* 界面设计器实战篇 基础篇 【界面设计器】模型增删改查基础 【界面设计器】他表字段 【界面设计器】左树右表 【界面设计器】树形表格 【界面设计器】树下拉/级联选择 【界面设计器】自定义字段组件基础 展示篇 【界面设计器】自定义字段组件实战——轮播图 【界面设计器】自定义字段组件实战——表格字段组合展示 【界面设计器】自定义字段组件实战——表格字段内嵌表格 交互篇 【界面设计器】自定义字段组件实战——千分位输入框 其他 前端低无一体使用教程 如何自定义表格字段? 【界面设计器】组件开发常见问题 【前端】低无一体部署常见问题 【前端】生产环境性能调优 API文档 OioProvider详解(v4.3.0)* 前端环境配置(v4)* 默认布局模板(v4) 表格主题配置(v4) 运行时上下文API文档(v4) Class Component(ts)(v4)

    2024年5月25日
    5.9K00

Leave a Reply

Please Login to Comment