Erlo

从零到一自建云表格系统

2022-09-06 11:30:12 发布   247 浏览  
页面报错/反馈
收藏 点赞

从零到一自建云表格系统

大部分业务系统、管理后台 70% 的功能是数据表格的增删改查。这些功能的实现,以复制粘贴修改字段为主。本文将尝试用一种更优雅的 “复制粘贴” 方式编写此类功能。如果你希望告别枯燥乏味的重复劳动,或者需要自建一套云表格系统,接下来的内容可以帮到你。

DEMO 演示

通过表格编辑器,在线设计制作表格类功能模块。适用于用户管理、供应商管理、订单管理等表格 CURD 操作的功能模块。

image.png

res.png

源码地址: https://github.com/YaoApp/demo-widget

**一键部署: **https://letsinfra.com/openapp/demo-widget

构建工具

YAO 开源应用引擎

yao-arch-white.png YAO 是一款开源应用引擎,使用 Golang 编写,以一个命令行工具的形式存在, 下载即用。适合用于搭建业务系统、网站/APP API 接口、管理后台等应用程序。

YAO 采用 flow-based 的编程模式,通过编写 YAO DSL (JSON 格式逻辑描述) 或 JavaScript 处理器,实现各种功能。 YAO DSL 可以有多种编写方式:

  1. 纯手工编写
  2. 使用自动化脚本,根据上下文逻辑生成
  3. 使用可视化编辑器,通过“拖拉拽”制作

**GitHub 地址: ** https://github.com/yaoapp/yao
Github Stars: 4.3K 开源协议: Apache 2.0 官方文档: https://yaoappcnblogs.com/doc

YAO Widget

YAO 将通用功能模块抽象为 Widget,通过 YAO DSL 描述差异,快速复制各种功能。 在开发中,如果开发一个相似的功能模块,需要将已有的功能复制粘贴,批量替换差异内容。 YAO 提供一种新方式 ,使用 DSL 描述差异内容,快速“复制粘贴”一个功能模块,以此来提升开发效率。

**YAO 内建了一组 Widgets, **涵盖大部分应用程序开发常见功能。

内建 Widget 功能
model 数据结构建模
flow 数据逻辑编排
api RESTFul API
table 表格 CURD
chart 数据图表
login 用户登录
register 用户注册
task 并发任务
schedule 计划任务
socket Socket
websocket WebSocket

Widget 支持开发者自定义,可基于自身业务逻辑特征,自定义 DSL, 快速复制各种通用功能。本文将通过自定义 Widget 的方式,实现一套云表格系统。

实战: 极简实用的云表格系统

Part I: 制作 dyform Widget

准备工作: 安装 YAO 命令

阅读以下内容,需要具备基础编程能力,熟悉 RESTFul API ,关系数据库,JavaScript 语言等编程常识。 参考文档 入门指南 , 在本地安装 YAO 命令 ( 版本: v0.10.1),并熟悉基本使用方法。
**在本地创建一个应用: **

# 创建一个项目文件夹
mkdir -p /your/project/root

# 初始化项目
yao init

# 创建数据表 & 初始化菜单
yao migrate && yao run flows.setmenu

第一步: 设计 Widge DSL 数据结构

根据业务逻辑特征,设计 DSL 结构,本例中,不同表格间的差异为 表格字段、搜索器以及表格名称。 **dyform Widget DSL 数据结构: **

{
  "name": "TEST",
  "decription": "A TEST DYFORM",
  "columns": [
    {
      "title": "First Name",
      "name": "name",
      "type": "input",
      "search": true,
      "props": {
        "placeholder": "Please input your first name"
      }
    },
    {
      "title": "Amount",
      "name": "amount",
      "type": "input",
      "search": true,
      "props": {
        "placeholder": "Please input amount"
      }
    },
    {
      "title": "Description",
      "name": "desc",
      "type": "textArea",
      "props": {
        "placeholder": "Please input Description"
      }
    }
  ]
}

字段描述 DSL :

配置项 说明
title 在表格中显示的标题
name 字段名称, 对应数据库字段名
type 填写表单时,使用的组件
props 组件属性
search 该字段是否可以作为筛查项

创建 dyforms 文件,用于保存 DSL

cd /your/project/root
mkdir dyforms

将上面 DSL 保存到 **dyforms/demo.form.json **文件,用于调试。

第二步: 创建 dyform Widget

创建 dyform widget:

cd /your/project/root
mkdir -p widgets/dyform

# 创建 widget 文件
touch widgets/dyform/widget.json  # Widget 基本信息
touch widgets/dyform/compile.js   # dyform DSL 编译器
touch widgets/dyform/process.js   # dyform 处理器
touch widgets/dyform/export.js    # dyform 导出内建 Widget 定义

提示: YAO 自定义 widget 放置在 widgets 目录,需要手动创建这个目录。

使用编辑器打开 ** dyform/widget.json** 填写 dyform widget 基本信息

{
  "label": "Dynamic Form",
  "description": "A form widget. users can design forms online",
  "version": "0.1.0",
  "root": "dyforms",
  "extension": ".form.json",
  "modules": ["Models", "Tables"]
}

**字段说明: **

配置项 说明
label Widget 名称,在可视化编辑器显示。
description Widget 介绍, 在可视化编辑器显示。
version 版本号
root DSL 文件保存路径(相对于项目根目录)
extension DSL 文件扩展名
modules 需要导出的模块。 在 export.js 中根据 DSL 描述,转换的 YAO 内建 widgets。 如 model, table 等。这些 widgets 与保存在项目目录中的 DSL 文件等效。

第三步: 编写 dyform 处理器

编写 **dyform/process.js **脚本,实现 **Model, Table **处理器,将自定义 DSL 转换为 YAO 数据模型 widget 和 表格 widget。

编写 Export 函数,声明要导出的处理器, 查看源码

function Export() {
  return { Model: "Model", Table: "Table" };
}

实现 Model & Table 处理器逻辑

处理器 说明 源码链接
Model 将 dyform DSL 转换为 model DSL, 等效于在 models 文件夹手动编写一个 xxx.mod.json DSL 文件 查看源码
Table 将 dyform DSL 转换为 table DSL, 等效于在 tables 文件夹手动编写一个 xxx..json DSL 文件 查看源码

提示: 建议使用 yao run 命令调试处理器,参考源码注释文档。

第四步: 导出为 Model & Table widget

编写 dyform/export.js 脚本, 实现 Models, Tables 函数,导出为 YAO model & table widgets.

导出函数 说明 源码链接
Models 调用 Model 处理器,导出 Yao model widget, 数据结构为 {MODEL_NAME:String: Model_DSL:Object} 查看源码
Tables 调用 Table 处理器, 导出为 Yao table widget , 数据结构为: {TABLE_NAME:String: Table_DSL:Object} 查看源码

效果预览

在 dyforms 文件夹下,添加 **xxx.form.json **文件, 编写 dyform DSL 描述文件,即可快速创建一组表格管理模块、表格 API、表格处理器和模型处理器。

demo.png

使用 yao migrate 命令创建数据表结构,yao start 命令启动服务,登录管理界面,即可使用表格管理功能。 路由地址: http://127.0.0.1:5099/xiang/table/dyform.xxx

Part II: 将 dyform DSL 保存到数据库,实现动态读写

如果希望把表格制作功能开放给用户,且在通常情况下代码目录应设定为只读,这就无法动态创建 DSL 文件。对 dyform widget 稍加改造,将 dyform DSL 保存在数据库中即可。

第一步: 创建一个 template model, 存储 dyform DSL

如何创建使用数据模型 models/template.mod.json 和管理表格 tables/template.tab.json, 参考Model 文档Table 文档 models/template.mod.json 如下

{
  "name": "Template",
  "table": { "name": "template", "comment": "For dyform DSL source" },
  "columns": [
    { "label": "ID", "name": "id", "type": "ID", "comment": "ID" },
    { "label": "Name", "name": "name", "type": "string", "index": true },
    {
      "label": "DSL",
      "name": "dsl",
      "type": "text",
      "comment": "DSL"
    }
  ],
  "values": [],
  "option": { "timestamps": true, "soft_deletes": true }
}

DSL 管理界面 admin.png

第二步: 给 dyform Widget 添加 Save & Delete 处理器

编写 Export 函数,声明要导出的处理器, 查看源码

function Export() {
  return { Model: "Model", Table: "Table", Save: "Save", Delete: "Delete" };
}

实现 Save & Delete 处理器逻辑

处理器 说明 源码链接
Save 根据 dyform DSL 转换为 model DSL, 更新对应数据模型结构,同时重新加载对应实例。 查看源码
Delete 删除数据模型对应数据表 查看源码

编辑 tables/template.tab.json,添加 hooks 脚本,在表格保存和删除时,更新/删除数据表,创建/删除菜单。

**修改 tables/template.tab.json 添加 Hook **查看源码** **

  "hooks": {
    "after:save": "scripts.template.AfterSave",
    "after:delete": "scripts.template.AfterDelete"
  },

**添加 hooks 脚本 scripts/template.js **查看源码

Hook 说明
AfterSave 根据 DSL ,更新 dyform 数据表,并添加菜单
Delete 删除 dyform 数据表,并移除菜单

第三步: 给 dyform Widget 添加内容源

编辑 widgets/dyform/compile.js , 从数据库中读取 DSL

/**
 * Source
 * Where to get the source of DSL
 */
function Source() {
  var sources = {};
  tpls = Process("models.template.Get", { select: ["id", "dsl"], limit: 1000 });
  if (tpls.code && tpls.message) {
    log.Error("Load dyform sources: %s", tpls.message);
    return sources;
  }

  tpls.forEach((tpl) => {
    tpl = tpl || {};
    try {
      instance = `instance_${tpl.id}`;
      dsl = JSON.parse(tpl.dsl);
      sources[instance] = dsl;
    } catch (e) {
      log.Error("Source %v DSL: %s", tpl.id, e.message);
      return;
    }
  });

  return sources;
}

**Compile.js 方法说明 **查看文档

Hook 说明
Source 自定义 DSL 数据源读取逻辑
Compile 根据上下文或环境信息,调整 DSL 结构或准备相关资源
OnLoad 当 DSL 加载完成,调用此方法。

效果预览

登录管理后台,动态添加 dyform DSL ,即可动态创建删除对应表格。 admin2.png res.png

Part III: 可视化表单编辑器

可以使用表单制作工具来生成 form DSL。有很多优秀的开源表单编辑器可以选择,比如 XXXX , 为了演示效果,XGEN 快速实现了一个 DEMO 级的编辑器组件,产品级的实现将在 XGEN-NEXT (正式版) 发布时提供。 源码地址: https://github.com/YaoApp/xgen/tree/main/src/cloud/components/form/FormPrinter image.png

第一步: 适配编辑器组件输入输出

为 template 表格添加 Hook, 用来转换 DSL 结构。 编辑 tables/template.tab.json 文件、 scripts/template.js文件

**添加 **before:save** hook ** 将编辑器组件输出,转换为 form DSL
tables/template.tab.json

{
  ...
	"hooks": {
    "before:save": "scripts.template.BeforeSave",
    "after:save": "scripts.template.AfterSave",
    "after:delete": "scripts.template.AfterDelete"
  }
  ...
}

scripts/template.js

/**
 * BeforSave Hook: transform the form editor data to the DSL
 * @param {*} payload
 */
function BeforeSave(payload) {
  payload = payload || {};
  columns = payload.dsl || [];
  payload["dsl"] = { columns: [], name: payload.name || "UNTITLE" };
  columns.forEach((column) => {
    let type = column.id || "";
    payload["dsl"].columns.push({
      title: column.title || "UNTITLE",
      name: column.bind,
      type: type.toLowerCase(),
      props: column.props || {},
    });
  });
  payload["dsl"] = JSON.stringify(payload["dsl"]);
  return [payload];
}

tips : 可是使用 yao run命令调试例如:

yao run scripts.template.BeforeSave '::{"dsl":...}'

添加 **after:find** hook 将 form DSL 转换为组件输入结构

tables/template.tab.json

{
  ...
  "hooks": {
    "after:find": "scripts.template.AfterFind",
    "before:save": "scripts.template.BeforeSave",
    "after:save": "scripts.template.AfterSave",
    "after:delete": "scripts.template.AfterDelete"
  }
  ...
}

scripts/template.js

/**
 * AfterFind Hook: transform the DSL format to the form editor needs
 * @param {*} id
 * @param {*} template
 */
function AfterFind(template, id) {
  let dsl = JSON.parse(template["dsl"]);
  let columns = dsl.columns || [];
  let types = { input: "Input", select: "Select" };

  template["dsl"] = [];
  columns.forEach((column) => {
    // let props = column.props || {};
    // let search = props.showSearch ? true : false;
    template["dsl"].push({
      title: column.title,
      bind: column.name,
      id: types[column.type],
      props: column.props || {},
      search: true,
      width: 6,
      chosen: false,
      selected: false,
    });
  });

  return template;
}

第二步: 更新 DSL 绑定的组件

编辑 tables/template.tab.json 文件, 将 DSL 字段编辑组件设定为 FormPrinter。 源码地址: https://github.com/YaoApp/demo-widget/blob/main/tables/template.tab.json

{
  ....
  "Form Editor": {
      "label": "Form Editor",
      "edit": { "type": "FormPrinter", "props": { "value": ":dsl" } }
  }
  ...
}

效果预览

打开 templates 表格,点击编辑一个模板,即可使用可视化编辑器制作表单。 image.png

Part IV: 制作 C 端表单页面

对于活动报名、预约注册等 C 端页面,需要个性化设计来提升用户体验。对于此类场景,可以有针对性的设计一组模板,使用任意前端技术栈实现。这里提供一个 VUE3 实现的 DEMO。 **源码地址: **https://github.com/YaoApp/demo-widget-front

第一步: 添加一个 API,用来读取表格配置信息

添加一个 GET /page/form/setting/:name API , 在 apis 文件夹下,添加一个 page.http.json 文件, 添加一个接口,第一个参数为 表格名称.

{
  "name": "Table Setting",
  "version": "1.0.0",
  "description": "Table Setting",
  "group": "page",
  "guard": "-",
  "paths": [
    {
      "path": "/form/setting/:name",
      "method": "GET",
      "guard": "-",
      "process": "xiang.table.Setting",
      "in": ["$param.name", ":query"],
      "out": { "status": 200, "type": "application/json" }
    }
  ]
}

源码地址: https://github.com/YaoApp/demo-widget/blob/main/apis/page.http.json

第二步: 在 SETUP 时,请求配置接口,获取配置信息。

const url = `${window.location.protocol}//${window.location.host}/api/page/form/setting/${formName}`;
const response = fetch(url);
response
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    formTitle.value = data.name;
    const fieldset = data?.edit?.layout?.fieldset || [];
    const columns = fieldset.length > 0 ? fieldset[0]?.columns : [];
    const mapping = data?.columns || {};
    columns.forEach((col: any) => {
      let column = mapping[col.name] || false;
      if (column?.edit) {
        let name = column.edit.props?.value || "";
        column.edit.label = col.name;
        column.edit.field = name.replace(":", "");
        column.edit.type = components[column.edit.type];
        formItems.value.push(column.edit);
      }
    });

    loading.value = true;
  })
  .catch((err) => {
    console.log("ERR", err);
    failure.value = err.message;
  });

源码地址: https://github.com/YaoApp/demo-widget-front/blob/main/vue3/src/App.vue

第三步: 动态渲染表单

使用 component 组件,根据配置渲染表单


源码地址: https://github.com/YaoApp/demo-widget-front/blob/main/vue3/src/App.vue

第四步: 将制品复制到 UI 目录

YAO 内建了一个 HTTP server,将制品复制到 ui 目录即可访问

npm run build
cp -r dist ../demo-widget/ui/vue3

源码地址: https://github.com/YaoApp/demo-widget/tree/main/ui/vue3

效果预览

打开浏览器,输入 http://your-host:port/xiang/login/admin 进入后台,录入并保存一个表单。 默认用户名: xiang@iqka.com 默认密码: A123456p+

image.png

打开 http://your-host:port/vue3/?form=dyform.instance_1 即可访问自定义表单页面 image.png

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认