從零到一自建雲表格系統
從零到一自建雲表格系統
大部分業務系統、管理後臺 70% 的功能是資料表格的增刪改查。這些功能的實現,以複製貼上修改欄位為主。本文將嘗試用一種更優雅的 “複製貼上” 方式編寫此類功能。如果你希望告別枯燥乏味的重複勞動,或者需要自建一套雲表格系統,接下來的內容可以幫到你。
DEMO 演示
通過表格編輯器,線上設計製作表格類功能模組。適用於使用者管理、供應商管理、訂單管理等表格 CURD 操作的功能模組。
原始碼地址: http://github.com/YaoApp/demo-widget
**一鍵部署: **http://letsinfra.com/openapp/demo-widget
構建工具
YAO 開源應用引擎
YAO 是一款開源應用引擎,使用 Golang 編寫,以一個命令列工具的形式存在, 下載即用。適合用於搭建業務系統、網站/APP API 介面、管理後臺等應用程式。
YAO 採用 flow-based 的程式設計模式,通過編寫 YAO DSL (JSON 格式邏輯描述) 或 JavaScript 處理器,實現各種功能。 YAO DSL 可以有多種編寫方式:
- 純手工編寫
- 使用自動化指令碼,根據上下文邏輯生成
- 使用視覺化編輯器,通過“拖拉拽”製作
**GitHub 地址: ** http://github.com/yaoapp/yao
Github Stars: 4.3K 開源協議: Apache 2.0 官方文件: http://yaoapps.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、表格處理器和模型處理器。
使用 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 管理介面
第二步: 給 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 ,即可動態建立刪除對應表格。
Part III: 視覺化表單編輯器
可以使用表單製作工具來生成 form DSL。有很多優秀的開源表單編輯器可以選擇,比如 XXXX , 為了演示效果,XGEN 快速實現了一個 DEMO 級的編輯器元件,產品級的實現將在 XGEN-NEXT (正式版) 釋出時提供。 原始碼地址: http://github.com/YaoApp/xgen/tree/main/src/cloud/components/form/FormPrinter
第一步: 適配編輯器元件輸入輸出
為 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。 原始碼地址: http://github.com/YaoApp/demo-widget/blob/main/tables/template.tab.json
{
....
"Form Editor": {
"label": "Form Editor",
"edit": { "type": "FormPrinter", "props": { "value": ":dsl" } }
}
...
}
效果預覽
開啟 templates 表格,點選編輯一個模板,即可使用視覺化編輯器製作表單。
Part IV: 製作 C 端表單頁面
對於活動報名、預約註冊等 C 端頁面,需要個性化設計來提升使用者體驗。對於此類場景,可以有針對性的設計一組模板,使用任意前端技術棧實現。這裡提供一個 VUE3 實現的 DEMO。 **原始碼地址: **http://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" }
}
]
}
原始碼地址: http://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;
});
原始碼地址: http://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>
原始碼地址: http://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
原始碼地址: http://github.com/YaoApp/demo-widget/tree/main/ui/vue3
效果預覽
開啟瀏覽器,輸入 http://your-host:port/xiang/login/admin
進入後臺,錄入並儲存一個表單。 預設使用者名稱: [email protected]
預設密碼: A123456p+
開啟 http://your-host:port/vue3/?form=dyform.instance_1
即可訪問自定義表單頁面