深浅模式
功能组成
每个典型的功能,至少由4个部分组成:
- .vue 文件, 基本功能模块前端文件,这个文件允许手写复杂前端逻辑、样式、复杂html等,他回引用 .design.js 文件,共同拼凑成一个前端功能模块;
- .design.js 文件, 这个文件是由凯乐士视图设计器生成. 都是以 vjson 格式存储;
- .java 文件, 后端接口逻辑,这个文件是手写的,并且与前端功能模块对应,共同完成权限控制、数据交互等功能;
- .xml 文件, Mybatis的SQL文件,这个文件会被二开设计器写入,同时也允许手写,当遇到复杂 SQL 时,可以手写定制 SQL;
vue 文件
例如,模块名称为 CurrentTask.vue, 内容如下
vue
<template>
<YvVjsonLayout :vjson="vjson" :vcxt="vcxt"/>
</template>
<script>
import {YvI18nMixin, BizMixin} from 'yvan-workbench'
import Design from './CurrentTask.design' // 引用同路径同名的 .design.js 文件, 在这个例子中就是 CurrentTask.design
export default {
mixins: [Design, YvI18nMixin, BizMixin],
mounted() {
},
data() {
return {}
},
methods: {}
}
</script>
design.js 文件
例如,模块名称为 CurrentTask.vue, 内容如下
javascript
export default {
data() {
return {
// 模块组件的引用句柄.
// 如果组件定义了 reference 属性,则自动生成支持组件句柄,放到 refs 中,方便编程时使用
// 每个引用属性,都会带上 @type 注释以支持在 IDE 环境中的代码提示
refs: {
/**
* @type {aggridplus}
*/
grid1: null,
/**
* @type {form}
*/
form1: null,
/**
* @type {dialog}
*/
dialog1: null,
/**
* @type {textfield}
*/
textbox1: null,
/**
* @type {combo}
*/
combo2: null,
},
// 数据部分,一部分高级组件(比如对话框 dialog / aggridplus 组件,会自动将一些数据绑定到模块数据中,让其具备双向绑定特性)
grid1: {
isChanged: false,
selections: [],
checkedRows: [],
selection: null,
},
dialog1: {
isShow: false,
loading: false,
width: false,
title: false,
row: {},
},
// 数据部分,vjson中定义的 bind.xxx 会双向绑定到某个属性上
query: {
value1: '',
value2: '',
},
// 视图定义部分
vjson: {
title: 'N/A', // 模块标题
isFlex: true, // 是否自适应高度,开启这个选项之后,布局模式最高高度是 100%,且不会出现纵向滚动条
layout: 'DefaultLayout', // 布局模式,( PC页面=DefaultLayout; 移动端=MobileLayout, 大屏页面=DashboardLayout )
// 页面中包含的子对话框组件
dialogs: [
{
reference: 'dialog1', // reference 属性如果有值,会在模块 refs 中生成一个属性,方便编程时使用组件引用
xtype: 'drawer', // xtype 既组件类型,所有的组件都会有 xtype,具体见 defineVjsonComponent 定义 vjson 组件
// 对话框内的组件
items: [
{
xtype: 'aggridplus',
reference: 'grid1',
listeners: {
rowClick: '{grid1_rowClick}',
},
}
]
}
],
// 所有下级组件
items: [
{
xtype: 'form',
// form 组件的内部组件
items: [
{
xtype: 'textfield',
reference: 'textbox1',
fieldLabel: '查询条件1',
bind: {
value: '{query.f1}', // 文本输入框中的值,双向绑定到 query.f1 属性
},
listeners: {
change: '{f1_change}', // change 事件绑定到 f1_change 方法
},
},
{
xtype: 'combo',
reference: 'combo2',
fieldLabel: '查询条件2',
displayField: 'text', // 下拉框中显示的字段名
valueField: 'id', // 下拉框中的值字段名
dataType: 2, // 下拉框数据类型(1=自定义枚举字典; 2=数据字典; 3=界面引用表达式; 5=URL, 设置 optionUrl)
dataDict: 'DIV_TYPE', // 当下拉框数据类型是2,这个属性是数据字典的名称
required: true,
bind: {
value: '{query.f2}', // 下拉框中的值,双向绑定到 query.f2 属性
disabled: '{combo2_disabled}',
},
listeners: {
change: '{f2_change}', // change 事件绑定到 f2_change 方法
},
}
]
}
]
}
}
},
computed: {
combo2_disabled() {
return (this.refs.grid1.selections ?? []).length === 0
},
},
methods: {
grid1_rowClick() {
},
f1_change() {
},
f2_change() {
},
}
}
详细解读
详细解读
- 每个功能模块的 design.js 是作为 vue 模块的一个 mixins, 混入到 vue 功能模块中的。因此 design.js 有完整的 vue 混入模块的结构,他会有自己的 data() / computed / watch / methods 等基本方法;
- design.js 返回的 data 固定有 vjson / refs 属性, 还有一些扩展出来的自定义属性
- refs: 模块组件的引用句柄. 如果组件定义了 reference 属性,则自动生成支持组件句柄,放到 refs 中,方便编程时使用
- vjson: 视图定义部分,是一个 json 对象,包含了模块的所有视图定义. vjson 属性, 固定有 title / isFlex / layout / dialogs / items 属性
- title: 模块标题
- isFlex: 是否自适应高度,开启这个选项之后,布局模式最高高度是 100%,且不会出现纵向滚动条
- layout: 布局模式,( PC页面=DefaultLayout; 移动端=MobileLayout, 大屏页面=DashboardLayout )
- dialogs: 页面中包含的子对话框组件
- items: 所有下级组件
- 其他属性,可能是 bind.xxx 中定义的可双向绑定属性,也可能高阶组件允许双向绑定的属性,比如 grid1.checkedRows. 具体见 组件定义
- 在 vjson 中,每个组件都有固定的4个属性 ( xtype / reference / bind / listeners ),以及一些动态属性
- xtype: 组件类型
- reference: 组件引用,如果有值,会在模块 refs 中生成一个属性,方便编程时使用组件引用
- bind: 组件数据绑定,双向绑定到某个 vue 属性上,属性可能在data中或 vue 计算属性(computed)中。 比如 bind.value = '{query.f1}' 就是绑定到 query.f1 属性上, bind.value = '{combo2_disabled}' 就是绑定到 combo2_disabled 属性上, 可能这个属性是个计算属性. 如果计算属性只有 get 方法,没有 set 方法, 有可能会造成程序异常。
- listeners: 组件事件监听,绑定到当前功能模块(vue) 的 methods 方法中, 语法为: "{方法名}", 这个方法可能位于 design.js 中,也可以在 vue 文件中
组件定义 defineVjsonComponent
javascript
/**
* 在 PC 端布局下,xtype='button' 的组件定义
*/
export default defineVjsonComponent('button', 'web', {
/**
* 运行时组件
*/
runtime: 'YvVjsonButton',
/**
* 设计时组件
*/
designer: 'YvDesignButton',
/**
* 用于 app.use() 注册至全局组件
*/
install(Vue) {
Vue.component('YvVjsonButton', YvVjsonButton)
Vue.component('YvDesignButton', YvDesignButton)
},
/**
* 组件的工具箱定义
*/
Toolbox: [
{
// 注册到基本工具面板,在低代码拖拽时回调 onCreate 方法进行创建
category: 'basic', order: 1, icon: '/assets/yv-design-icon/ic_light_button.png', dragGroup: "fieldGroup",
onCreate() {
const vjson = _.defaults({
xtype: 'button',
text: 'NewButton',
}, Button)
return addVJsonId(vjson)
}
},
],
/**
* 组件在生成文档大纲时,如何用字符串表示
*/
getLabel(props) {
let ret = ''
if (props.text) {
ret = ret + props.text + ' '
}
if (props.reference) {
ret = ret + props.reference + ' '
}
return _.trim(ret)
},
/**
* 组件在设计时,右侧展示的属性面板描述
*/
propertyPanel: {
/**
* 组件在设计时,"基础属性" 面板描述
*/
basicPanel: [
...basicPanelBase,
{name: 'hidden', label: '隐藏', editor: 'Switch', bindable: true},
{name: '-'},
{
name: 'text', label: '文本', editor: 'TextInput', bindable: true,
desc: '按钮的文本'
},
{name: 'iconCls', label: '图标', editor: 'Icon', bindable: true},
{
name: 'isActionLog', label: '记录操作日志', editor: 'Checkbox', bindable: false,
desc: '按钮点击后系统会记录下操作日志, 用于审计. 开启这个选项之后,一定要填写"操作名"'
},
{
name: 'actionName', label: '操作名', editor: 'TextInput', bindable: false,
desc: '记录操作日志时的操作名',
showIf(vjson) {
return vjson.isActionLog
},
},
{
name: 'type', label: '显示类型', editor: 'Select', bindable: true,
desc: '按钮的颜色, 目前有: default / primary / success / info / warning / danger',
options: [
{value: 'default', label: '默认'},
{value: 'primary', label: '主要'},
{value: 'success', label: '成功'},
{value: 'info', label: '信息'},
{value: 'warning', label: '警告'},
{value: 'danger', label: '危险'},
]
},
{name: '-'},
{
name: 'shortcut', label: '快捷键', editor: 'ShortCutCmp', bindable: false,
desc: '按钮的快捷键'
},
{
name: 'plain', label: '朴素', editor: 'Switch', bindable: true,
desc: '朴素按钮将没有背景色和边框,鼠标悬停时会显示背景色'
},
{
name: 'round', label: '圆角', editor: 'Switch', bindable: true,
desc: '开启这个选项之后,按钮会变成圆角按钮'
},
{
name: 'circle', label: '圆形', editor: 'Switch', bindable: true,
desc: '开启这个选项之后,按钮会变成圆形按钮',
},
{
name: 'link', label: '文本模式', editor: 'Switch', bindable: true,
desc: '开启这个选项之后,按钮会变成文本模式,没有背景色和边框,鼠标悬停时会显示背景色',
},
{
name: 'loading', label: '加载中', editor: 'Switch', bindable: true,
desc: '开启这个选项之后,按钮会至于不可用状态,并且显示一个加载中的图标',
},
'-',
{
name: 'size', label: '尺寸', editor: 'ButtonGroup', bindable: true,
desc: '按钮的尺寸, 目前只有 large / default / small 三种',
options: [
{value: "large", label: "大"},
{value: "default", label: "中"},
{value: "small", label: "小"},
{value: "custom", label: "自定义"},
]
},
{
name: 'customSize', label: '自定义大小', editor: 'TextInput', bindable: true,
desc: '自定义按钮大小', showIf: (vjson) => vjson.size === 'custom'
},
{
name: 'customIconSize', label: '图标大小', editor: 'TextInput', bindable: true,
desc: '自定义按钮图标大小', showIf: (vjson) => vjson.size === 'custom'
},
],
/**
* 组件在设计时,"数据属性" 面板描述
*/
dataPanel: [],
/**
* 组件在设计时,"高级属性" 面板描述
*/
advancedPanel: [],
/**
* 组件在设计时,"样式属性" 面板描述
*/
stylePanel: [
{className: 'cls', styleName: 'style'},
],
/**
* 组件拥有的事件
*/
eventPanel: [
...eventPanelBase,
{name: 'click', label: '点击时触发', params: []},
],
/**
* 组件拥有的方法
*/
methodPanel: [
...methodPanelBase,
{name: 'performClick', label: '触发点击', desc: '触发点击事件'},
]
}
})
组件定义 defineVjsonComponent
defineVjsonComponent 方法接受3个参数,分别是
- xtype:组件名
- 组件类型(web=PC端组件; mobile=移动端组件; dashboard=大屏组件)
- 组件详细定义
- designer 设计时组件
- runtime 运行时组件
- Toolbox 工具箱定义
- childrenProps 子元素属性名称集
- install 用于
- properties 组件的属性描述
- getLabel 组件的标签描述
- propertyPanel 属性面板组件
- basicPanel 组件在设计时,"基础属性" 面板描述
- name: 属性名
- label: 属性标签
- editor: 属性编辑器
- bindable: 是否可绑定(该属性是否可以用于 bind.xxx), 默认为 false
- desc: 属性详细中文描述文档
- dataPanel 组件在设计时,"数据属性" 面板描述
- advancedPanel 组件在设计时,"高级属性" 面板描述
- stylePanel 组件在设计时,"样式属性" 面板描述
- methodPanel 组件拥有的方法
- eventPanel 组件拥有的事件
- basicPanel 组件在设计时,"基础属性" 面板描述
java 示例
java
package com.yvan.workbench.test;
import com.yvan.data.jdbc.DaoFactory;
import com.yvan.data.jdbc.MyBatis;
import com.yvan.data.jdbc.QueryDSL;
import com.yvan.model.response.Model;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public class Test2 {
private static final QueryDSL queryDSL = DaoFactory.getQueryDSL();
private static final MyBatis mybatis = DaoFactory.getMyBatis(Test2.class);
public static Model<Object> grid1QueryData(Map<String, Object> params) {
List<Map<String, Object>> list = mybatis.queryMany("grid1QueryData", params);
return Model.newSuccess(list);
}
}
mybatis 示例
xml
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="">
<select id="grid1QueryData">
select * from yv_user
</select>
</mapper>
- 凯乐士开发平台主要使用 QueryDSL 和 MyBatis 框架与数据库交互. QueryDSL 主要应付数据的添加、更新、删除,及简单的数据查询, MyBatis 主要支持复杂的 SQL,可以与低代码设计器结合.
- DaoFactory.getQueryDSL() 获取 QueryDsl 工具类。DaoFactory.getMyBatis(class) 获取当前类的 MyBatis 帮助类.
- design.js 中的 aggridplus 组件,设定了 dataSource 属性,可以直接对接后台的 static 方法,方法中将分页、筛选、排序等细节都在框架中处理掉了
编码规范
API函数库
示例
组件名 | 说明 |
---|---|
example1.md | 示例1.基础资料 |
example2.md | 示例2.开发表单 |
example3.md | 示例3.开发ASN单据界面 |
example4.md | 示例4.主表、子表模式的开发 |
example5.md | 示例5.平板应用开发 |
前端组件表
组件名 | 说明 |
---|---|
button | 按钮 |
textfield | 文本输入框 |
titlefield | 标题字段 |
treefield | 树形下拉框 |
checkbox | 勾选框 |
combo | 下拉框 |
combogrid | 下拉表 |
combosearch | 自定义搜索框 |
datefield | 日期输入框 |
daterangefield | 日期范围输入框 |
datetimefield | 日期时间输入框 |
fieldslot | 字段插槽 |
filefield | 文件上传 |
numberfield | 数字输入框 |
radiogroup | 单选输入组 |
staticfield | 静态文本字段 |
form | 表单组件 |
toolbar | 工具条 |
tree | 树 |
split | 分割面板 |
alert | 提示框 |
tabs | 选项卡 |
scrollbar | 滚动容器 |
aggrid | 表格 |
aggridplus | 高级表格 |
divider | 横向分割条 |
divider_formitem | 表单横向分割条 |
iframe | Iframe容器 |
slot | 插槽 |
img | 图片组件 |
yvlist | 列表组件 |