您的位置:  首页 > 技术杂谈 > 正文

从零到一自建云表格系统

2022-09-06 12:00 https://my.oschina.net/yaoapps/blog/5573272 YAO官方 次阅读 条评论

从零到一自建云表格系统

大部分业务系统、管理后台 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://yaoapps.com/doc

YAO Widget

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

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

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

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"]
}

**字段说明: **

配置项说明
labelWidget 名称,在可视化编辑器显示。
descriptionWidget 介绍, 在可视化编辑器显示。
version版本号
rootDSL 文件保存路径(相对于项目根目录)
extensionDSL 文件扩展名
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 组件,根据配置渲染表单

<template>
  <div v-if="loading">
    <div class="header">{{ formTitle }}</div>
    <form>
      <div class="form-item" v-for="item of formItems" :key="item.label">
        <div class="label">{{ item.label }}</div>
        <component :is="item.type" :name="item.field"></component>
      </div>
      <div class="form-item" v-if="formItems">
        <button class="button" type="button">Submit</button>
      </div>
    </form>
  </div>
  <div v-else-if="failure" class="failure">{{ failure }}</div>
  <div v-else>
    <div>Loading...</div>
  </div>
</template>

源码地址: 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

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接