GraphQL请求详解(v4)

阅读之前

什么是GraphQL?

Oinone官方解读
GraphQL入门

可视化请求工具

insomnia下载

概述

(以下内容简称GQL)

众所周知,平台的所有功能都是通过一系列元数据定义来驱动的,而GQL作为服务端和客户端交互协议,其重要性不言而喻。下面会从以下几个方面介绍GQL在平台中是如何运作的:

  • 服务端定义元数据生成GQL对应的schema
  • 通过HttpClient发起一个GQL请求
  • 通过window.open使用GET方式发起一个GQL请求
  • 客户端泛化调用任意API服务
  • 客户端通过运行时上下文RuntimeContext发起GQL请求

准备工作

在开始介绍GQL之前,我们需要定义一个可以被GQL请求的服务端函数,以此来介绍我们的相关内容。(对服务端不感兴趣的同学可以跳过代码部分)

DemoModel.java
@Model.model(DemoModel.MODEL_MODEL)
@Model(displayName = "演示模型", labelFields = {"name"})
public class DemoModel extends IdModel {

    private static final long serialVersionUID = -7211802945795866154L;

    public static final String MODEL_MODEL = "demo.DemoModel";

    @Field(displayName = "名称")
    private String name;

    @Field(displayName = "是否启用")
    private Boolean isEnabled;
}
DemoModelAction.java
@Model.model(DemoModel.MODEL_MODEL)
public class DemoModelAction {

    @Action(displayName = "启用")
    public DemoModel enable(DemoModel data) {
        data.setIsEnabled(true);
        data.updateById();
        return data;
    }

    @Action(displayName = "禁用")
    public DemoModel disable(DemoModel data) {
        data.setIsEnabled(false);
        data.updateById();
        return data;
    }
}

上面的java代码定义了演示模型字段动作

  • 字段 field
    • id:ID 整数 Integer
    • name:名称 字符串 String
    • isEnabled:是否启用 布尔 Boolean
  • 动作 action
    • enable:启用 提交动作 ServerAction
    • disable:禁用 提交动作 ServerAction

服务端定义元数据生成GQL对应的schema

模型和字段
type DemoModel {
    id: Long
    name: String
    isEnabled: Boolean
}
动作
type DemoModelInput {
    id: Long
    name: String
    isEnabled: Boolean
}

type DemoModelQuery {
    queryOne(query: DemoModelInput): DemoModel
    ......
}

type DemoModelMutation {
    enable(data: DemoModelInput): DemoModel
    disable(data: DemoModelInput): DemoModel
    ......
}

PS:平台内置了多种QueryMutation定义,通过模型继承关系将自动生成,无需手动定义。比如Query定义包括queryOnequeryPage等;Mutation定义包括createupdate等。特殊情况下,默认逻辑无法满足时,服务端通常采用函数重载的方式进行替换,客户端则无需关心。

生成规则

  • type DemoModel:通过模型编码demo.DemoModel.分隔后的最后一位,并转换为大驼峰格式。字段与声明类型一致。
  • type DemoModelInput动作入参定义,未做特殊声明的情况下与模型定义一致。
  • type DemoModelQuerytype DemoModelMutationQueryMutation为固定后缀,分别生成动作相关类型。当函数类型为QUERY时,使用Query后缀,其他情况使用Mutation后缀。

(此处仅做简单解释,详细生成规则过于复杂,客户端无需关心)

Query类型的GQL示例

query {
  demoModelQuery {
    queryOne(query: { id: ${id} }) {
      id
      name
      isEnabled
    }
  }
}

Mutation类型的GQL示例

mutation {
  demoModelMutation {
    enable(data: { id: ${id} }) {
      id
      name
      isEnabled
    }
  }
}

GQL请求规则

  • querymutationGQL默认关键字,若为query则可缺省。
  • demoModelMutation:通过DemoModelMutation类型定义转换为小驼峰获取。Query类似。
  • enable:通过服务端定义的函数名称获取,以上示例中函数名称Java函数名称一致,特殊情况下,服务端可使用@Function(name = "")注解或@Action.Advanced(name = "")注解进行修改。disable类似。
  • (data: { id: ${id} })data通过服务端定义的参数名称获取,与Java函数参数名称一致。值类型与服务端声明类型一致,对象类型采用{}包裹,对象中的每个属性类型与服务端声明类型同样一致。此处服务端声明的data名称的入参仅包含DemoModelInput中定义的属性。GQL是通过参数名称进行参数映射的,与参数位置无关。
  • 响应字段声明:idnameisEnabled为本次请求后获取到的相关字段,不在响应字段声明中的属性无法被获取。

PS:GQL在客户端发起的请求中提供了VariablesFragmentInline Fragment等方式进行GQL的复用。感兴趣的同学可在GQL官网自行学习。

TypeScript最佳实践

通常情况下,我们是不需要手动发起GQL的,但在某些特殊场景(无法获取元数据上下文等)中,这是无法避免的。

在我们学习如何发起GQL请求之前,我们需要达成一个基本共识,即ts类型声明API服务归集

  • ts类型声明:需按照模型定义类型声明,通常我们将这些类型声明放在typing.ts文件中进行管理。
  • API服务归集:需按照模型对应的服务分别定义相关API服务的调用方法,并将这些服务放在service.ts文件中进行管理。具体的方法根据实际情况进行判断,但不建议使用复杂入参定义服务方法。
  • 模块名称归集:需将所有API中用到的模块名称统一放在module-name.ts文件中进行管理。

工程结构

service目录将为该工程提供全部的API服务。

这是我们建议的工程结构,具体工程结构也可以根据自身情况进行相应变更。接下来,我们会在通过HttpClient发起一个GQL请求章节提供相关的示例代码作为参考。

├── src
│   ├── service
│   │   ├── demo-model
│   │   │   ├── index.ts
│   │   │   ├── service.ts
│   │   │   └── typing.ts
│   │   ├── model.ts
│   │   ├── module-name.ts
│   │   └── index.ts
│   ├── main.ts
│   └── shim-vue.d.ts
├── tsconfig.json
└── vue.config.js

通过HttpClient发起一个GQL请求

service/module-name.ts
export const DEMO = "demo";
service/index.ts
export * from './demo-model';
service/demo-model/typing.ts
export interface DemoModel {
    id?: string;
    name?: string;
    isEnabled?: string;
}
service/demo-model/service.ts
import { HttpClient } from '@kunlun/dependencies';
import { DEMO } from '../module-name';
import { DemoModel } from './typing';

const http = HttpClient.getInstance();

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    const gql = `mutation {
  demoModelMutation {
    enable(data: {id: "${id}"}) {
      id
      name
      isEnabled
    }
  }
}`;
    const res = await http.mutate<DemoModel>(DEMO, gql);
    return res.data.demoModelMutation.enable;
  }
}
service/demo-model/index.ts
export * from './typing';
export * from './service';

PS:对于GQL字符串的构造,平台还提供了另一种构造方式,开发者可根据自身喜好进行选择。

service/model.ts(模型名称归集,与模块名称归集类似)
export const DEMO_MODEL_NAME = 'demoModel';
service/demo-model/service.ts
import { GQL } from '@kunlun/dependencies';
import { DEMO_MODEL_NAME } from '../model';
import { DEMO } from '../module-name';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    return GQL.mutation(DEMO_MODEL_NAME, 'enable')
      .buildRequest((builder) => builder.buildObjectParameter('data', (builder) => builder.stringParameter('id', id)))
      .buildResponse((builder) => builder.parameter('id', 'name', 'isEnabled'))
      .request(DEMO);
  }
}
使用对应服务
import { DemoService } from './service';

// 1. async/await (推荐)
const res = await DemoService.enable('1');
console.log(res);

// 2. Promise#then
DemoService.enable('1').then((res) => {
    console.log(res);
});

至此,我们已经可以发起任何GQL请求了。

通过window.open使用GET方式发起一个GQL请求

使用HttpClient发起的请求不论是使用query还是mutate,都是POST方式的请求。对于文件下载等需要使用GET方式的请求,我们通常使用window.open方法,并打开新的浏览器窗口来实现这一功能。

const gql = ${GQL};

window.open(UrlHelper.appendBasePath(/pamirs/${MODULE_NAME}?query=${encodeURIComponent(gql)}), '_blank');

客户端泛化调用任意API服务

GQL中定义的模型字段过多时,我们往往会发觉直接手写一个GQL的难度是相当高的,为此,我们提供了一个较为简单的调用服务方式,以辅助生成我们所需的GQL

该方法采用动态获取元数据的方式实现的泛化调用,仅限于开发环境使用,不适合生产环境使用。获取元数据的相关API服务相对于业务来说是一些无意义的损耗,因此,我们通常建议在开发时通过该方式生成GQL后,从请求体中拷贝该GQL,并改为通过HttpClient方式进行调用。一般而言,生成一次的GQL后,在未来的变更中仅会有个别字段的变化,不会产生过大的变化。

service/model.ts(模型名称归集,与模块名称归集类似)
export const DEMO_MODEL = 'demo.DemoModel';
service/demo-model/service.ts
import { GenericFunctionService } from '@kunlun/dependencies';
import { DEMO_MODEL } from '../model';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(id: string): Promise<DemoModel | undefined> {
    return GenericFunctionService.INSTANCE.executeByName(DEMO_MODEL, 'enable', { deep: 1 }, { id });
  }
}

这种方式需要使用模型编码函数名称作为入参,deep参数控制响应字段声明的层级,最后的不定长参数作为函数的入参。

由于泛化调用服务会动态获取模型关联模型函数等相关信息,函数名称参数名称等信息无法提前预知,也就无法明确定义参数名称,因此需要根据参数位置进行入参匹配。

小结

以上两种方式均是在没有元数据的情况下使用的请求方式,从之前的文章中我们可以了解到,元数据仅在main-view组件中才进行了收集,其他的情况是无法正常获取元数据的。

比如我们通常在mask中定义的组件,app-switchermenu等,这些组件需要发起相关请求,但其元数据无法在页面中获取。

不仅如此,这种请求方式只有在业务场景确定模型不再发生变更,并且确定该服务与权限相关功能完全无关时才可以使用。

由于我们无法提前预知该服务的相关变化,这些问题是无法避免的:

  • 当模型字段发生变化时,在代码中定义的GQL也需要发生相应的变更。
  • 当权限限制该用户发起该请求时,请求结果只会报错,我们无法预知服务是否可以正常调用。

客户端通过运行时上下文RuntimeContext发起GQL请求

在可以获取到元数据的情况下,我们可以用下面这种方式发起GQL请求。

service/demo-model/service.ts
import {
  GenericFunctionService,
  isRuntimeServerAction,
  RuntimeContext,
  RuntimeServerAction
} from '@kunlun/dependencies';
import { DemoModel } from './typing';

export class DemoModelService {
  public static async enable(runtimeContext: RuntimeContext, id: string): Promise<DemoModel | undefined> {
    const action = runtimeContext.model.modelActions.find(
      (v) => isRuntimeServerAction(v) && v.functionDefinition?.name === 'enable'
    ) as RuntimeServerAction;

    const functionDefinition = action?.functionDefinition;

    if (!functionDefinition) {
      return;
    }

    const requestModelFields = runtimeContext.getRequestModelFields();

    return GenericFunctionService.INSTANCE.execute(
      functionDefinition,
      {
        model: runtimeContext.model,
        requestFields: requestModelFields
      },
      { id }
    );
  }
}

在这个示例中,我们通过runtimeContext.model.modelActions查找所需调用的动作,如果该动作在当前运行时上下文中未找到,那我们就能知道这个动作可能在当前页面中未配置无权限访问。并且从运行时上下文中获取到的请求模型字段,可以完全按照页面配置进行按需请求。这也是我们推荐使用的调用方式之一。

需要注意的是,在GenericFunctionService#execute可选项中requestFields属性优先于deep属性。

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

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

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

相关推荐

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

    在 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日
    87500
  • 【界面设计器】自定义字段组件实战——表格字段组合展示

    阅读之前 此文章为实战教程,已假定你熟悉了【界面设计器】较为完整的【自定义组件】相关内容。 如果在阅读过程中出现的部分概念无法理解,请自行学习相关内容。【前端】文章目录 业务背景 表格中的一列使用多个字段组合展示。 演示内容:表格中存在两列,【编码】和【基础信息】。将【名称】、【创建时间】、【更新时间】在【基础信息】一列展示。 业务分析及实现思路 从需求来看,我们需要实现一个【组合列】组件,并且该组件允许在【表格】视图中使用。由于【组合列】本身也是一个字段,因此这里需要选择需要组合字段中的其中一个字段作为组件切换的基础字段,比如我们可以选择【名称】字段作为基础字段。 在【组合列】组件的属性面板中,我们需要再自定义一个【组合列配置】组件,用来选择需要将哪些字段进行组合,以及为每个组合提供一些基础配置。 这里需要理解一个基本概念,即【组合列】的属性面板是【组合列配置】的【执行页面】。所有组件的属性面板在【执行页面】时都是【表单】视图。 因此我们可以实现一个【组合列配置】组件,并且该组件允许在【表单】视图中使用。其业务类型使用【文本】,我们在保存配置数据时,可以使用JSON数据结构来存储复杂结构。(这里的实现思路并非是最符合协议设定的,但可以满足绝大多数组件场景) 在【组合列配置】组件中,我们可以允许用户添加/移除组合,并且每个组合有两个属性,【标题】和【字段】。 准备工作 此处你应该已经在某个业务模型下,可以完整执行当前模型的全部【增删改查】操作。 业务模型定义 (以下仅展示本文章用到的模型字段,忽略其他无关字段。) 名称 API名称 业务类型 是否多值 长度(单值长度) 编码 code 文本 否 128 名称 name 文本 否 128 创建时间 createDate 日期时间 否 – 更新时间 updateDate 日期时间 否 – 实现页面效果展示 表格视图 创建组件、元件 准备工作完成后,我们需要根据【业务背景】确定【组件】以及【元件】相关信息,并在【界面设计器】中进行创建。 以下操作过程将省略详细步骤,仅展示可能需要确认的关键页面。 创建组合列组件 创建组合列元件 创建组合列配置组件 创建组合列配置元件 设计组合列元件属性面板 创建compositeConfig字段,并切换至【组合配置】组件。 设计组合列配置元件属性面板 启动SDK工程进行组件基本功能开发 PS:这里由于我们创建了两个组件,因此,将SDK分开下载后,然后将组合列配置SDK中的演示代码(kunlun-plugin/src)移动到组合列SDK中,在同一工程中进行开发,最后只需将相关JS文件和CSS文件上传到组合列组件中即可,组合列配置组件可以不进行上传。这里需要注意的是,上传多个包含相同组件功能的JS文件和CSS文件可能在运行时导致无法正常替换、冲突等问题。 (npm相关操作请自行查看SDK工程中内置的README.MD) 开发步骤参考 打开【表格】视图,将【名称】字段的组件切换为【组合列】组件。 在属性面板中看到【组合列配置】组件,并优先实现【组合列配置】组件。这里的属性面板就是【组合列配置】对应的【执行页面】。 当【组合列配置】组件可以按照预先设计的数据结构正确保存compositeConfig属性时,可以在【组合列】组件中的props定义中直接获取该属性,接下来就可以进行【组合列】组件的开发。 代码实现参考 工程结构 typing.ts export interface CompositeConfig { key: string; label?: string; field?: string; value?: string; } CompositeColumnConfig.vue <template> <div class="composite-column-config"> <oio-form v-for="item in list" :data="item" :key="item.key"> <oio-form-item label="标题" name="label"> <oio-input v-model:value="item.label" /> </oio-form-item> <oio-form-item label="字段" name="field"> <a-select class="oio-select" dropdownClassName="oio-select-dropdown" v-model:value="item.field" :options="fields" /> </oio-form-item> <oio-button type="link" @click="() => removeItem(item)">移除</oio-button> </oio-form> <oio-button type="primary" block @click="addItem">添加</oio-button> </div> </template> <script lang="ts"> import { uniqueKeyGenerator } from '@kunlun/dependencies'; import { WidgetInstance } from '@kunlun/ui-designer-dependencies'; import { OioButton, OioForm, OioFormItem, OioInput } from '@kunlun/vue-ui-antd'; import { Select as ASelect } from 'ant-design-vue'; import { computed, defineComponent, PropType, ref, watch } from 'vue'; import { CompositeConfig } from '../../typing'; export default defineComponent({ name: 'CompositeColumnConfig', components: { OioForm, OioFormItem, OioInput, OioButton, ASelect }, props: { currentInstance: { type:…

    2023年11月1日
    1.6K00
  • oio-cascader 级联选择

    级联选择框。 何时使用 需要从一组相关联的数据集合进行选择,例如省市区,公司层级,事物分类等。 从一个较大的数据集合中进行选择时,用多级分类进行分隔,方便选择。 比起 Select 组件,可以在同一个浮层中完成选择,有较好的体验。 API <oio-cascader :options="options" v-model:value="value" /> 参数 说明 类型 默认值 Version allowClear 是否支持清除 boolean true autofocus 自动获取焦点 boolean false changeOnSelect (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 boolean false disabled 禁用 boolean false displayRender 选择后展示的渲染函数,可使用 #displayRender="{labels, selectedOptions}" ({labels, selectedOptions}) => VNode labels => labels.join(' / ') dropdownClassName 自定义浮层类名 string – getTriggerContainer 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 Function(triggerNode) () => document.body loadData 用于动态加载选项,无法与 showSearch 一起使用 (selectedOptions) => void – maxTagCount 最多显示多少个 tag,响应式模式会对性能产生损耗 number | responsive – maxTagPlaceholder 隐藏 tag 时显示的内容 v-slot | function(omittedValues) – multiple 支持多选节点 boolean – options 可选项数据源 – placeholder 输入框占位文本 string ‘请选择’ searchValue 设置搜索的值,需要与 showSearch 配合使用 string – showSearch 在选择框中显示搜索框 boolean false tagRender 自定义 tag 内容,多选时生效 slot – value(v-model:value) 指定选中项 string[] | number[] – showSearch showSearch 为对象时,其中的字段: 参数 说明 类型 默认值 filterOption 接收 inputValue path 两个参数,当 path 符合筛选条件时,应返回 true,反之则返回 false。 function(inputValue, path): boolean 事件 事件名称 说明 回调参数 版本 change 选择完成后的回调 (value, selectedOptions) => void – search 监听搜索,返回输入的值 (value) => void – Option interface Option { value: string | number; label?: any; disabled?: boolean; children?: Option[]; // 标记是否为叶子节点,设置了 `loadData` 时有效 // 设为 `false` 时会强制标记为父节点,即使当前节点没有 children,也会显示展开图标 isLeaf?: boolean; }

    2023年12月18日
    1.4K00
  • Oinone平台之Router扩展

    问题描述 在Oinone平台内置路由中,默认了三种路由 /login //默认登录页 /page //默认主逻辑页 / //根页面,会自动发起查询优先级最高的应用,并跳转 在实际的业务迭代中,我们通常有以下三种需求: 我要覆盖默认的登录页,页面我不喜欢,登录逻辑满足不了; 我要在平台上加个帮助中心; 这个路径不符合我司规范,我要自定义加前缀 接下来,我将在Oinone平台中满足以上场景 覆盖默认路径 以登录页为例 在项目目录src/main.ts下,添加自定义router import 'ant-design-vue/dist/antd.css'; import 'element-plus/dist/index.css'; import '@kunlun/vue-ui-antd/dist/kunlun-vue-ui-antd.css'; import '@kunlun/vue-ui-el/dist/kunlun-vue-ui-el.css'; import 'reflect-metadata'; import { VueOioProvider } from '@kunlun/dependencies'; import interceptor from './middleware/network-interceptor'; import './field'; import './view'; import './actions'; VueOioProvider( { http: { url: location.origin, callback: interceptor }, browser: { title: 'Oinone – 构你想象!', favicon: 'https://pamirs.oss-cn-hangzhou.aliyuncs.com/pamirs/image/default_favicon.ico' }, router: [{ path: '/login', widget: 'CustomLogin'}] // 用CustomLogin覆盖默认登录页 }, [] ); 定义CustomLogin, 定义方式同书籍中的自定义表单和自定义表格类似,精简版的代码为: import { RouterWidget, SPI } from "@kunlun/dependencies"; @SPI.ClassFactory(RouterWidget.Token({ widget: 'CustomLogin' })) // SPI注册,router得widget和此处的widgetshi对应的 export class CustomLogin extends RouterWidget { public initialize(props) { super.initialize(props); this.setComponent('定义的vue文件'); return this; } } 增加新的访问路径 同覆盖登录页 在router中增加路由 router: [{ path: '/login', widget: 'CustomLogin'}, { path: '/help', widget: 'Help'}] 定义Help,同覆盖登录页 定义个性化路径 需要再所有访问路径前统一加标识,比如添加Oinone;在项目目录下新建.env文件(若存在,可以复用),在env文件中添加: BASE_PATH=/Oinone 修改后重启工程即可,访问/Oinone/login即可 结语 以上就是Oinone平台路由的扩展能力,在Oinone平台中,通过自定义Router达到扩展路由的能力,并通过采用env等通用配置的能力,解决批量修改路由的目的。

    2023年11月1日
    60.7K00
  • 【前端】默认布局模板(v4)

    默认母版(Mask) PC端默认母版 <mask> <multi-tabs /> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </container> </mask> PC端默认内联Tabs母版(<multi-tabs inline="true" />) <mask> <header> <widget widget="app-switcher" /> <block> <widget widget="notification" /> <widget widget="divider" /> <widget widget="language" /> <widget widget="divider" /> <widget widget="user" /> </block> </header> <container> <sidebar> <widget widget="nav-menu" height="100%" /> </sidebar> <block height="100%" flex="1 0 0" flexDirection="column" alignContent="flex-start" flexWrap="nowrap" overflow="hidden"> <multi-tabs inline="true" /> <content> <breadcrumb /> <block width="100%"> <widget width="100%" widget="main-view" /> </block> </content> </block> </container> </mask> 移动端默认母版 <mask> <widget widget="user" /> <widget widget="nav-menu" app-switcher="true" menu="true" /> <widget widget="main-view" height="100%" /> </mask> PC端默认布局(Layout) 表格视图(TABLE) <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" /> <element widget="rowActions" slot="rowActions" /> </element> </pack> </view> 表单视图(FORM) <view type="FORM"> <element widget="actionBar" slot="actionBar"> <xslot name="actions" /> </element> <element widget="form" slot="form"> <xslot name="fields" /> </element> </view>…

    2023年11月1日
    1.6K01

Leave a Reply

Please Login to Comment