【低代码】一切从文档开始!从清水房到精装,可视化 + 拖拽只是辅助
从数据库文档开始,要啥有啥
感觉大家对低代码好像有一点误解。
低代码的核心并不是可视化 + 拖拽,核心应该是“更少的代码实现更多的功能”!
使用低代码做项目,也不仅仅是拖拖拽拽,因为十几年前,微软就用 webform 证明了:拖拽,看起来挺好,但是实际上并不那么好用。
因为,如果一个表单里的众多字段,需要一个一个拽进来,那么想想是不是很痛苦?
低代码的正确的打开方式是什么呢?
以管理后台为例,我们做项目之前,是不是要写一份数据库文档,然后呢?不会束之高阁吧,其实一切都可以从文档开始!
相关文章
- 专栏 基于Vue3做一套低代码引擎
- 开篇
- 列表
- 表单
- 角色、权限和部门
从文档到增删改查
我习惯用 Excel 写数据库文档,因为用着方便,而且可以用代码的方式读取 Excel 的内容。 以前用 .net 读写,现在可以在前端直接读取了。
也就是说,我们可以读取数据库文档,然后依据文档内容生成基础 json 文件 清水房。
有了基础的 json 文件,然后用维护 JSON 的小工具,调整一下,增删改查就诞生了。精装修
流程:文档 =》 json =》 可视化 + 拖拽 + 手势 =》 列表、表单等
读取 Excel 文档
官方github:github.com/SheetJS/js-…
选择 js-xlsx 读取 Excel 是因为可以直接获得 json 形式的数据,不用自己拼接方便多了。
好像只能直接引入 js 文件的方式。
<script src="/js/xlsx.core.min.js"></script>
读取本地文件
/**
* 读取本地excel文件
* @param file 文件
* @param callback 回调
*/
export function readWorkbookFromLocalFile(file, callback) {
const reader = new FileReader()
reader.onload = function(e) {
const data = e.target.result
const workbook = XLSX.read(data, {type: 'binary'})
if(callback) callback(workbook)
}
reader.readAsBinaryString(file)
}
读取远程文件
/**
* 读取网络文件
* @param url 远程文件的URL
* @param callback 回调
*/
export function readWorkbookFromRemoteFile(url, callback) {
const xhr = new XMLHttpRequest()
xhr.open('get', url, true)
xhr.responseType = 'arraybuffer'
xhr.onload = function(e) {
if(xhr.status == 200) {
var data = new Uint8Array(xhr.response)
var workbook = XLSX.read(data, {type: 'array'})
if(callback) callback(workbook)
}
}
xhr.send()
}
设计与实现
还是老规矩,先设计接口和状态,然后写代码实现功能。
设计接口
为了更好的记录各种信息,我们设计几个接口,避免过几天自己都想不起来都有啥属性。
- ISheetNames 工作表
type idType = number | string
type valueType = number | string | boolean | null | Array<valueType> | any
/**
* 工作簿:表的分组,
*/
export type ISheetNames = Array<string>
/**
* 工作簿里的表的集合
*/
export interface ISheets {
[sheetName: string]: Array<ITable>
}
- ITable 数据库的 表
/**
* 表的类型
*/
export interface ITable {
tableID: idType, // 表编号
tableName: string, // 表名
cnName: string, // 中文名称
content: string, // 表的说明
cols: IColumn[] // 字段集合
}
- IColumn 数据库的字段
/**
* 字段类型
*/
export interface IColumn {
tableId: idType, // 表编号
columnId: idType, // 字段编号
colName: string, // 字段名称
cnName: string, // 中文名称
colType: string, // 字段类型,nvarchar、int 等
colSize: number, // 大小
defaultValue: valueType, // 默认值
canNull: boolean, // 允许空
controlTypeId: number, // 控件类型
content: string // 字段说明
}
- IMetaDataState 状态
/**
* 状态
*/
export interface IMetaDataState {
sheetNames: ISheetNames,
sheets: ISheets,
tables: {
[tableId: idType]: ITable
},
current: {
sheetName: string, // 当前选择的工作簿名称
table: ITable, // 当前选择的表的信息,包括 字段集合
col: IColumn // 当前选择的字段,一个字段
}
}
设计状态
为了便于各个组件之间互通数据,我们先设计一套状态:
import type { InjectionKey } from 'vue'
import { watch } from 'vue'
// 状态
import { defineStore, useStoreLocal } from '@naturefw/nf-state'
import type { IState } from '@naturefw/nf-state/dist/type'
// 类型
import type {
IColumn,
ITable,
ISheets,
IMetaDataState
} from '../types/10-meta'
// 状态的标识,避免命名冲突
const flag = Symbol('meta-data') as InjectionKey<string>
/**
* 注册局部状态
* * 读取文件用的状态 : IMetaDataState & IState
* @returns
*/
export const regMetaDataState = (): IMetaDataState & IState => {
// 定义 角色用的状态
const state = defineStore<IMetaDataState>(flag, {
state: (): IMetaDataState => {
return {
sheetNames: [], // 工作簿的名称集合
sheets: {}, // 工作簿的集合,包括表、字段
tables: {}, // 表的集合,包括字段
current: { // 当前的部门的信息
sheetName: '', // 选择的工作簿的名称
table: { // 当前选择的表的信息,包括 字段集合
tableID: '', // 表编号
tableName: '', // 表名
cnName: '', // 中文名称
content: '', // 表的说明
cols: [] // 字段集合
}, // 序号
col: { // 当前选择的字段,一个字段
tableId: '', // 表编号
columnId: '', // 字段编号
colName: '', // 字段名称
cnName: '', // 中文名称
colType: '', // 字段类型,nvarchar、int 等
colSize: 4, // 大小
defaultValue: '', // 默认值
canNull: false, // 允许空
controlTypeId: 101, // 控件类型
content: '' // 字段说明
}
}
}
},
getters: {
},
actions: {
}
},
{ isLocal: true }
)
return state
}
/**
* 子组件获取状态
*/
export const getMetaDataState = (): IMetaDataState & IState => {
return useStoreLocal<IMetaDataState & IState>(flag)
}
分析文档内容,转换为表和字段
接口和状态设计好之后,我们写转换代码。
- 获取 Excel 里面的 “工作表”
// 建立 sheet 集合 state.sheets
const toSheet = (state, sheetNames) => {
// state.sheetNames
state.sheetNames.length = 0
state.sheetNames.push(...sheetNames)
// state.sheets
sheetNames.forEach(name => {
state.sheets[name] = []
})
}
- 获取Excel 里面的 表和字段
// 建立表和字段
const toTable = (json, state, sheetName) => {
// 遍历记录集
json.forEach(code => {
if (code['字段编号'] === 0) {
// 这条记录是表
const newTable = {
tableID: code['表编号'], // 表编号
tableName: code['字段名'], // 表名
cnName: code['中文名'], // 中文名称
content: code['说明']??'', // 表的说明
cols: [] // 字段集合
}
state.tables[code['表编号']] = newTable
state.sheets[sheetName].push(newTable)
} else {
// 这条记录是字段
let columnId = ''
if (code['表编号']) {
columnId = code['表编号']
if (code['字段编号']) {
columnId += code['字段编号'].toString().padStart(3,'0')
} else {
console.log('字段编号:', code['字段编号'])
}
} else {
console.log('表编号:', code['表编号'])
}
const col = {
tableId: code['表编号'], // 表编号
columnId: columnId, // 字段编号
colName: code['字段名'], // 字段名称
cnName: code['中文名'], // 中文名称
colType: code['类型'], // 字段类型,nvarchar、int 等
colSize: code['大小'], // 大小
defaultValue: code['默认值']??'', // 默认值
canNull: code['允许空'] === 0 ? false: true, // 允许空
controlTypeId: code['控件类型']?? 101, // 控件类型
content: code['说明'] // 字段说明
}
if (state.tables[code['表编号']]?.cols) {
state.tables[code['表编号']].cols.push(col)
} else {
console.log('出错:', code['表编号'])
}
}
})
}
- 先选择文件,然后可以展示工作簿、表和字段
依据文档建立json文件 —— 清水房
文档内容读取之后,我们可以创建各种json文件了。
表单
把数据库文档里面的“表”和“字段”,变成 JSON 文件的形式:
import { reactive, watch } from 'vue'
import { getMetaDataState } from './state/state-meta-data'
// 定义表单需要的 meta
const formMeta = reactive({
formMeta: {
moduleId: 142,
formId: 14210,
columnsNumber: 2,
colOrder: [],
linkageMeta: {},
ruleMeta:{}
},
itemMeta: {}
})
// 字段单独的meta
const colMeta = reactive({})
/**
* 设置 meta
* @param cols
* @param model
*/
const _toMetaCol = (cols) => {
// 清空
for(const key in model) {
delete model[key]
}
for(const key in colMeta) {
delete colMeta[key]
delete formMeta.itemMeta[key]
}
formMeta.formMeta.colOrder.length = 0
// 遍历
cols.forEach(col => {
const meta = {
formItemMeta: {
columnId: col.columnId,
colName: col.colName,
label: col.cnName,
controlType: col.controlTypeId,
isClear: false,
defValue: col.defaultValue,
colCount: 1
},
placeholder: '请填写' + col.cnName,
title: col.cnName
}
colMeta[col.columnId] = meta
formMeta.itemMeta[col.columnId] = meta
formMeta.formMeta.colOrder.push(col.columnId)
})
}
/**
* 创建表单的 meta
*/
export const toMetaForm = (model) => {
const state = getMetaDataState()
watch(state.current.table.cols, (cols) => {
resetModel(cols, model)
_toMetaCol(cols)
})
return {
formMeta,
colMeta
}
}
列表
还是根据表和字段,设置列表需要的json。
import { reactive, watch } from 'vue'
import { getMetaDataState } from './state/state-meta-data'
// 定义列表需要的 meta
const gridMeta = reactive({
gridMeta: {
moduleId: 142,
idName: 'ID',
colOrder: []
},
height: 400,
itemMeta: {}
})
// 模拟列表数据
const createData = (cols, i) => {
const list = {}
cols.forEach((col, index) => {
list[col.colName] = col.cnName + '_' + i
})
return list
}
/**
* 创建列表的 meta
*/
export const toMetaGrid = () => {
const state = getMetaDataState()
const dataList = reactive([])
const _toMetaCol = (cols) => {
// 清空
dataList.length = 0
for (let i = 0; i < 5; i++) {
dataList.push(createData(cols, i))
}
for(const key in itemMeta) {
delete itemMeta[key]
delete gridMeta.gridMeta[key]
}
gridMeta.gridMeta.colOrder.length = 0
gridMeta.gridMeta.idName = cols[0].colName
// 遍历
cols.forEach((col, index) => {
const meta = {
id: col.columnId,
colName: col.colName,
label: col.cnName,
width: 140,
title: col.cnName,
align: 'center',
'header-align': 'center'
}
gridMeta.itemMeta[col.columnId] = meta
gridMeta.gridMeta.colOrder.push(col.columnId)
})
}
watch(state.current.table.cols, (cols) => {
_toMetaCol(cols)
})
return {
dataList,
gridMeta
}
}
使用可视化 + 拖拽 —— 精装
这里就回到了上次介绍的维护JSON的小工具,目前实行了列表和表单的维护,
上次 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具 介绍过了,这里不重复。
转载自:https://juejin.cn/post/7124606808257364005