Oinone
产品
Oinone
Oinone 框架
企业级低代码开发框架
Aino
Aino
智能AI开发平台
案例定价博客社区
资源
📖
文档
开发指南与API参考
💬
支持
获取技术支持与服务
📄
更新日志
查看最新版本特性
🏡
关于
了解我们的团队与愿景
搜索 K
v7.0v6.0
简体中文English
安装与升级
社区版
企业版
从社区版转向企业版
Oinone 设计器配置指南
版本升级
版本清单
常见问题
用户手册
设计器
模型设计器
模型
数据字典
数据编码
界面设计器
视图管理
视图类型
视图设计
组件与模型总览
组件介绍
布局
字段
媒体
动作
自定义组件
自定义组件管理
自定义组件-元件信息
自定义组件-元件设计
应用菜单
打印模板管理
打印模板设计
流程设计器
流程管理
流程设计
节点动作
数据可视化
图表管理
图表类型
图表设计
报表
数据大屏管理
数据大屏设计
图表模板
集成设计器
工作台
连接器
数据流程
流程日志
开放平台
MCP
业务域
接口日志
微流设计器
微流管理
微流设计
AI集成设计器
连接器
AI配置
接口日志
表达式的使用说明
标准模块
工作台
个人中心
系统配置
管理中心
用户
合作伙伴
组织架构
角色与权限
工作流
工作流
工作流管理
消息
业务审计
文件
资源
翻译
集成应用
应用中心
应用环境
低无一体
研发手册
AI Coding 实战
Oinone遇上Trae的AI Coding最佳实践(后端)
Oinone遇上Qoder的AI Coding最佳实践(后端)
教程(Tutorials)
环境准备
Git安装与注意事项
JDK安装与注意事项
Maven安装与注意事项
Node.js安装与注意事项
设置指南(Setup Guide)
后端框架(Back-end framework)
章节 1:整体介绍(Architecture Overview)
章节 2:新建一个应用(A New Application)
章节 3:模型与基础字段(Models And Basic Fields)
章节 4:安全简介(A Brief Introduction To Security)
章节 5:界面实操(Finally, Some UI To Play With)
章节 6:基础视图(Basic Views)
章节 7:模型间关系(Relations Between Models)
章节 8:字段间联动(Field Interlinkage)
章节 9:准备学习行为(Ready For Some Action)
章节 10:约束(Constraints)
章节 11:追加细节(Add The Sprinkles)
章节 12:继承(Inheritance)
章节 13:模块间相互作用(Interact With Other Modules)
章节 14:产品的个性化开发(Customized Features)
探索前端框架(Discover the Front-end Framework)
章节 1: 组件(Widget)
章节 2:构建仪表盘(Build a dashboard)
精通前端框架(Master the Front-End framework)
章节 1:前端框架概览(Front-End Overview)
章节 2:创建甘特视图(Create a gantt view)
章节 3:自定义画廊视图(Customize a gallery view)
模块数据初始化(Init Module Data)
限制数据访问权限(Restrict access to data)
文件导入导出(Export and Import)
调试工具(Debug Tools)
操作指南
自定义字段
自定义动作
自定义视图
自定义GraphQL请求
自定义主题
组件默认主题变量
组件暗黑主题变量
自定义组件与设计器结合
参考指南(Reference)
后端API(Back-End framework)
模块 API(Module API)
ORM API
函数 API(Functions API)
动作 API(Actions API)
交互 API(UX API)
安全机制(Security in Oinone)
高级 API(Advance API)
网关协议 API(Protocol API)
请求上下文 API(Request Context API)
元位指令 API(Meta Directive API)
提示 API(Hint API)
消息中心 API(Message Hub API)
文件客户端 API(FileClient API)
消息队列 API(MQ API)
Redis API
ES API
通用工具 API(Tools API)
前端API(Front-End framework)
框架概览(Framework Overview)
环境(Environment)
上下文(Context)
组件(Widget)
Component Lifecycle
Basic
Mask
Layout
DSL
View
Element
Pack
Action
Field
Table Field
Search Field
Form Field
Detail Field
Gallery Field
Router
Oio 组件(Oio Components)
Vue UI Antd
Vue UI El
Vue UI
服务(Services)
Metadata Service
SPI Service
Router Service
GraphQL Service
HttpClient Service
RSQL Service
Message Hub Service
Event Bus Service
Stream Service (peer to peer)
Expression Service
Translate Service
User interface
视图架构(View architectures)
表格(Table)
表单(Form)
详情(Detail)
画廊(Gallery)
树(Tree)
UI icons
标准模块(Standard Modules)
用户与商业主体 API(User & Business API)
消息 API(Message API)
工作流(Workflow)
文件导入导出(Import And Export)
资源 API(Resources API)
集成接口 API(EIP API)
通用扩展点与平台SPI清单(Common Extension Points And SPI List)
平台错误码(Error Codes)
最佳范式
研发范式:研发流程
研发范式:模块化设计
研发范式:模型设计
软件公司:标准化与定制化共生的范式
常见解决方案
前端
全局layout:自定义树形组件,树形组件默认选中第一个值
发布:前端发布流程
字段:如何在多标签页切换时自动刷新视图
字段:打开的弹窗操作产生的数据回填给字段
应用:作为Iframe嵌入到已有系统中
应用:引入微前端qiankun
按钮:如何在业务模型的列表中展示工作流的审批按钮
按钮:跨页面传额外的参数
网络请求:OioProvider详解(自定义请求错误拦截)
网络请求:请求加密
视图:实现多个视图切换
视图:表格列合并
视图:表格列尾统计
视图:表格实现复制行
路由扩展:添加新路由,比如覆盖默认的登录页
后端
依赖配置:如何添加数据可视化运行时依赖
功能简介:数据可视化-项目中如何引用图表、报表、大屏
可视搭建:数据可视化中图表的低无一体
可视查询:数据可视化-如何自定义查询数据方法
场景应用:Excel添加水印功能
场景应用:函数之异步执行
场景应用:动态表单(相同的按钮跳转到不同的页面)
外部集成:企微集成OAuth2.0
外部集成:钉钉集成OAuth2.0
序列获取:如何在项目中手动获取序列
开发模式:协同开发(改)
开发规范:Function、Action函数使用规范
开发辅助:O2M、M2O、M2M关系字段配置问题以及问题排查路径
开发辅助:Oinone平台可视化调试工具
开发辅助:低代码中实现页面数据联动
开发辅助:低代码方式实现复制创建
开发辅助:使用GraphQL生成API文档
发布流程:后端发布流程
开发辅助:表单自动填充用户的相关信息
开发实践:业务实现多租户方案
开放接口:EIP开放接口使用MD5验签发起请求
搜索增强:Oinone引入搜索引擎(增强模型)
搜索增强:引入搜索(增强模型Channel)常见问题解决办法
数据操作:DsHint(指定数据源)和BatchSizeHint(指定批次数量)
数据操作:Excel导入导出模板翻译
数据操作:Excel批量导入
数据操作:IWrapper、QueryWrapper和LambdaQueryWrapper使用
数据操作:Oinone连接外部数据源方案
数据操作:分库分表与自定义分表规则
数据操作:复杂Excel模版定义
数据操作:复杂字段类型的导入导出
数据操作:多Sheet导入导出示例
数据操作:多表关联查询方案
数据操作:如何使用位运算的数据字典
数据操作:如何自定义Excel导入导出功能
数据操作:查询时自定义排序字段和排序规则
数据操作:自定义RSQL占位符(placeholder)及在权限中使用
数据操作:自定义SQL(Mapper)语句
数据操作:非存储字段搜索(unstore field seach)
数据方言:【DM】后端部署使用Dameng数据库(达梦)
数据方言:【KDB】后端部署使用Kingbase数据库(人大金仓/电科金仓)
数据方言:【MSSQL】后端部署使用MSSQL数据库(SQLServer)
数据方言:【OpenGauss】后端部署使用OpenGauss高斯数据库
数据方言:【PostgreSQL】后端部署使用PostgreSQL数据库
文件存储:MINIO无公网访问地址下OSS的配置
文件存储:OSS(CDN)配置和文件系统的一些操作
权限扩展:函数如何跳过权限拦截
权限扩展:如何删除系统权限中默认的首页节点
权限扩展:如何扩展行为权限
权限扩展:如何给角色增加菜单权限
权限扩展:如何跳过固定路径的权限
树表配置:树表怎么配置
校验定制:如何自定义表达式实现特殊需求?扩展内置函数表达式
流程扩展:如何添加工作流运行时依赖
流程扩展:工作流审核撤回-回退-拒绝钩子使用
流程扩展:流程扩展自定义函数示例代码汇总
流程扩展:通过业务数据操作工作流程(催办、撤销等)
流程配置:项目中工作流引入和流程触发
消息定制:如何推送自定义消息
源码配置:如何使用源码的方式配置表达式
环境保护:Oinone环境保护(v5.2.3以上)
环境升级:缓存连接由Jedis切换为Lettuce
环境部署:东方通Web和Tomcat部署Oinone项目
环境部署:中标麒麟(mips64架构)部署设计器
环境部署:后端无代码设计器Jar包启动方法
登录扩展:Oinone登录扩展:对接SSO
研发框架:框架之MessageHub(信息提示)
网络请求:第一次登录强制修改密码
自增ID:如何在项目中使用自增ID
认证集成:SSO单点登录
配置说明:Dubbo配置详解(改)
配置说明:函数之触发与定时配置和示例
页面设计:全局首页及应用首页配置方法(homepage)
页面设计:如何实现页面间的跳转
页面设计:自定义用户中心菜单
项目整合:Nacos做为注册中心:如何调用其他系统的SpringCloud服务?
项目整合:Oinone如何支持构建分布式项目
项目整合:Oinone项目引入Nacos作为注册中心
项目部署:Docker部署常见问题
项目部署:Oinone离线部署设计器JAR包
项目部署:Oinone离线部署设计器镜像
项目部署:Oinone设计器部署参数说明
项目部署:数据可视化的导入导出
项目部署:流程设计器的导入导出
项目部署:界面设计器的导入导出
常见问题
启动时:Oinone License 许可证使用常见问题
启动时:启动依赖错误的问题
启动时:调度服务器未分配任务队列一直刷日志
启动时:后端启动常见问题
开发中:多值字段、字段默认值如何配置
开发中:如何获取请求的模型
开发中:模型拷贝工具类
开发中:索引、唯一索引、联合索引如何创建
开发中:获取当前登录用户信息
环境准备:windows环境npm安装依赖提示无权限
环境迁移:导入设计数据时dubbo超时导入失败
运维相关:平台有健康检查接口吗
运行时:Dubbo服务找不到
运行时:保存多值字段SQL执行报错
运行时:如何使用 GQL 工具正确发起请求
运行时:如何实现同步下载Excel
运行时:导出任务处于处理中状态
运行时:找不到字段:Unknown field ‘xx’
运行时:权限配置不生效
运行时:界面设计器选不到模型
运行时:跳转动作无权限问题排查
运行时:配置了上下文参数但是值未传到跳转页面
第三方开源软件及许可说明
贡献手册
贡献者许可协议模板
研发贡献
Git guidelines
Coding guidelines
文档贡献
Content guidelines
软件使用许可和合约
  1. 首页/
  2. 研发手册/
  3. 教程(Tutorials)/
  4. 精通前端框架(Master the Front-End framework)/
  5. 章节 3:自定义画廊视图(Customize a gallery view)
本页目录

在 Oinone 中,画廊视图是一种通过卡片形式展示数据的视图类型。通过界面设计器或后端 DSL 同样可以对卡片内容进行设计,但这有时并不能满足我们的业务场景,毕竟并不是所有卡片都设计的那么“整齐”。那么,对画廊视图的卡片进行定制化开发,从某种程度上来说,这是非常有必要的。

让我们先来看看这个练习最终展示的效果吧。

一、准备工作 ​

在 Oinone 中,所有元数据都是围绕模型展开的。因此,我们需要准备一个简单的模型和一些视图,并且需要创建一个菜单,让我们可以在页面上展示这个模型的视图。

好了,让我们先着手构建出这样一个原始页面吧。

提示

这些准备工作并不是这个练习需要学习的内容,如果你对这些内容还不是很熟悉,这里有些学习内容供你参考:

模型设计器使用手册

界面设计器使用手册

后端框架(Back-End framework)

(一)准备模型(GalleryDemoModel) ​

你可以用模型设计器创建一个模型,也可以通过后端创建一个模型。

这个模型里面有编码、名称、个性签名以及头像这四个字段。

下面列出了在本次练习中,用到的模型(GalleryDemoModel)的字段信息:

名称API名称字段类型是否多值长度(单值长度)
编码code文本(String)否128
名称name文本(String)否128
个性签名description多行文本(Text)否-
头像avatar文本(String)否512

提示

这里需要注意头像字段在使用文本类型时需要指定字段长度为512或更大,否则在上传图片时可能会出现由于字段长度不足而无法正常保存的问题。

(二)准备视图 ​

除了模型之外,我们还需要一些视图,并且通过跳转动作将他们连接起来。你可以用界面设计器来完成这一系列的操作,也可以通过后端完成。

首先,我们需要一个画廊视图,用于展示本次练习的成果。

其次,我们还需要一个表单视图,用于填写并创建一些测试数据。

最后将这个画廊视图绑定在菜单上,让我们可以通过点击菜单看到这个视图。

二、切换自定义组件 ​

(一)通过 registerLayout 切换自定义组件 ​

像我们之前在 “探索前端框架” 章节练习的那样,我们可以通过注册这样一个布局(Layout)来切换组件,让我们将 widget="card" 改为 widget="GalleryCustomCard" 来完成组件的切换:

xml
<view type="gallery">
    <view type="search">
        <element slot="search" widget="search" />
    </view>
    <element widget="actionBar" slot="actionBar" />
    <element widget="gallery" slot="gallery">
        <element widget="GalleryCustomCard" slot="card">
            <template slot="title" />
            <template slot="content" />
            <template slot="rowActions" />
        </element>
    </element>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13

(二)通过后端 DSL 切换自定义组件 ​

让我们在 <template slot="card"> 上添加 widget="GalleryCustomCard" 属性。一个可能的 DSL 模板如下所示:

xml
<view model="demo.GalleryDemoModel" type="gallery">
    <template slot="actionBar">
        <action name="redirectCreatePage" label="创建" />
    </template>
    <template slot="gallery">
        <template slot="card" widget="GalleryCustomCard">
            <template slot="content">
                <field data="id" invisible="true" />

                <field data="code" label="编码" />
                <field data="name" label="名称" />
                <field data="description" label="个性签名" />
                <field data="avatar" label="头像" widget="UploadImg" />
            </template>
            <template slot="rowActions">
                <action name="redirectUpdatePage" label="编辑" />
                <action label="删除" name="delete" />
            </template>
        </template>
    </template>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

三、创建 GalleryCustomCard 组件 ​

和我们之前接触到的组件类似,卡片是一个 element 组件,并且我们需要使用内置组件的部分功能。因此我们可以这样定义:

typescript
import GalleryCustomCard from './GalleryCustomCard.vue';

@SPI.ClassFactory(
  BaseElementWidget.Token({
    viewType: ViewType.Gallery,
    widget: 'GalleryCustomCard'
  })
)
export class GalleryCustomCardWidget extends CardWidget {
  public initialize(props) {
    super.initialize(props);
    this.setComponent(GalleryCustomCard);
    return this;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

接下来,我们需要一个 Vue 组件模板来展示我们需要展示的信息,让我们先从一个最简单的展示开始吧。

vue
<template>
  <div class="gallery-custom-card-demo">
    <b>{{ formData.name }}({{ formData.code }})</b>
  </div>
</template>
1
2
3
4
5

内置组件中提供了 formData 属性,用于获取当前卡片数据,为了便于代码的维护,我们还需要声明其数据类型。

typescript
interface DemoData {
  code?: string;
  name?: string;
  description?: string;
  avatar?: string;
}

props: {
  formData: {
    type: Object as PropType<DemoData>
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

为了让我们的页面好看一点,我们可以使用这样的 css 样式美化一下。

css
.gallery-custom-card-demo {
  background-color: #ffffff;
  border: 1px solid #e3e7ee;
  border-radius: 4px;
  padding: 16px;
}
1
2
3
4
5
6

这样我们就得到了一个自定义的简易卡片了。

提示

更多关于卡片组件 API 的内容可参考:Element

四、使用 ActionBar 渲染底部动作区 ​

从界面设计器或后端DSL定义的 rowActions 行内动作区,我们可以通过 ActionBar 组件将其渲染在页面上。

在 Vue 组件模板中,我们可以这样使用:

vue
<div class="default-card-row-actions">
  <action-bar widget="CardRowActions" inline :active-records="formData" :row-index="rowIndex">
    <slot name="rowActions" />
  </action-bar>
</div>
1
2
3
4
5

还需要在 Vue 中声明组件:

typescript
import { ActionBar } from '@oinone/kunlun-dependencies';

components: {
  ActionBar
}
1
2
3
4
5

提示

这里使用的 CardRowActions 是内置组件,用于将动作渲染在页面上。

除了 formData 属性以外,这里还用到了 rowIndex,我们只需要在 props 中定义即可。它们都在 CardWidget 组件中被声明了。

为了保证样式完整,我们还需要使用 div 对这个组件进行包裹,并定义 class 为 default-card-row-actions。这些样式都是内置的,和原始页面完全一样。

五、完善 GalleryCustomCard 组件 ​

接下来,我们对这个卡片组件的一些样式进行完善,这一过程并没有一个标准答案,也不会有任何提示,你可以根据自己的想象力或者本章内容开头给出的图片继续将这个组件补充完整。

六、通过属性让组件变得通用 ​

组件通用化是一门组件抽象哲学,一个好的属性设计可以让组件适应大多数的情况,但这是一个需要长久体会的过程。通过这一部分内容,我们希望读者可以对 Widget 框架提供的配置灵活使用,并逐步体会组件通用化这样的开发体验。

(一)消除模型影响 ​

要想将卡片组件变得通用,消除模型影响是必然的。说的简单一些,就是不能让数据结构限制组件的取值,我们通常的做法是将这些需要的字段通过配置映射的方式让它变得更加灵活。

以 formData 取值为例,使用 formData.name 这样的做法会让我们写代码时变得很顺利,也牺牲了组件的灵活性。我们可以通过使用 formData[nameField] 这样的方式进行取值,而 nameField 是一个可以被配置的变量,这样我们的卡片组件,在取值就会变得非常灵活。当然了,这也在一定程度上牺牲了我们的研发习惯。这总是需要权衡的。

在 Widget 组件中,我们通过获取 DSL 中的配置对属性进行定义:

typescript
@Widget.Reactive()
public get title() {
  return `${this.formData[this.nameField]}(${this.formData[this.codeField]})`;
}

@Widget.Reactive()
public get codeField() {
  return this.getDsl().codeField || 'code';
}

@Widget.Reactive()
public get nameField() {
  return this.getDsl().nameField || 'name';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这样,在 Vue 组件模板中,我们可以直接使用 title 属性,来完成我们的标题定义。类似的,对于其他属性我们也可以根据属性功能对其进行语义化的定义。在这里就不一一列举了,读者可以自行思考和探索。

提示

如果可能的话,我们还希望可以通过表达式的方式配置标题格式,这样灵活性会更高。

更多关于表达式的内容可参考:Expression Service

(二)一种很糟糕但更加通用的做法 ​

当我们设计了一个需要复杂结构配置的属性时,通常我们可能无法通过 DSL 属性很好的配置,这里我们可以做一些取舍,在 DSL 可读性和功能性之间,我们选择功能性。

细心的读者可能已经发现,不论是通过界面设计器配置扩展属性,还是通过后端 DSL 配置扩展属性。由于 XML 语法本身的限制,我们只能配置简单值,对于结构化的复杂值我们只能通过约定来完成。

比如我们可以在卡片上设计一个 contentLayout 属性,这个属性接受的是一个 JSON 结构的数组,用它我们可以对中间内容区进行排版和布局。这一功能的实现可能会让我们“受益”终身,毕竟一个组件就可以涵盖多种布局方式,这样组件的复用程度也会显著提高。

但同样带来了一个问题,组件的维护难度也会显著提高,配置的难度也会显著提高。一个未经过设计的复杂结构配置会让这个组件变得无法长久的使用,甚至不可避免的成为不可迭代组件。

提示

在这里虽然我们提及了这种“约定高于配置”的做法,也还是希望读者可以对组件通用化有进一步的了解。这只是一种“不得已而为之”的做法,并不建议使用。

Pager
Previous page章节 2:创建甘特视图(Create a gantt view)
Next page模块数据初始化(Init Module Data)
本页目录