目前很多低代碼平臺都是基於Web用拖拽方式生成界面,確實可以極大的提高開發效率,但也存在一些問題:
- 大部分平臺靈活性不夠,特殊需求需要較大的自定義開發;
- 解析json配置的執行效率不是太高;
- 大部分平臺缺乏後端支持或複雜的業務邏輯支持;
- 與後端的數據結構及業務服務不存在強關聯,修改後端容易造成前端配置失效;
- 大部分平臺缺乏移動端及桌面端支持;
作者通過不斷嘗試及多年的經驗積累創建了AppBox項目,一個快速開發框架,其將應用系統所涉及的數據結構、業務邏輯、用戶界面、工作流、報表、權限等抽象爲各類型的模型,通過組合模型形成完整的應用系統,也可以在線修改模型以適應業務的需求變更。 由於模型具備規範性和關聯性約束,這樣可以高效的分析模型間的關係,並減少因修改模型時引入新的缺陷。本文以客戶信息管理作爲示例簡單介紹使用AppBox的開發流程,以便小夥伴們能夠快速瞭解本框架。
一、運行前準備
- 準備一個空的數據庫,目前僅支持PostgreSql;
- 克隆倉庫
git clone --recurse-submodules https://github.com/enjoycode/AppBox.git
- 編譯發佈WebHost項目;
- 編譯發佈BlazorApp項目,並將發佈目錄內的wwwroot文件夾複製到WebHost的發佈目錄內;
- 修改WebHost目錄內的
appsettings.json
文件中的數據庫鏈接; - 終端進入WebHost的發佈目錄,執行
dotnet AppBoxWebHost.dll
,首次執行會初始化數據庫並創建一些內置的模型(如下圖所示);
- 打開瀏覽器輸入開發環境入口
localhost:5000/#/dev
,登錄用戶名:Admin
密碼:760wb
懶得編譯請加作者微信或郵件直接發打包好的(本想用GitHub Release打包,但超過大小限制)
二、創建實體模型
實體模型用於描述數據結構,可映射存儲至指定數據庫,也可以不映射至數據庫(DTO)。參考下圖先選擇模型樹的Applications->sys->Entities文件夾,然後點擊頂部主菜單的New->Entity,在彈出的對話框內輸入實體名稱"Customer"並選擇映射的數據庫"Default"。
在實體設計器的工具條點擊"Add"按鈕添加實體成員,其中MemberType(成員類型)中的EntityField代表字段,EntityRef代表一對一引用,EntitySet代表一對多引用。
點擊實體設計器工具條點擊"Options"按鈕切換至選項面板,用於設置實體的主鍵及索引。
上述操作完成後,點擊主菜單Models->Save保存當前模型,並且點擊Models->Publish發佈當前實體模型,發佈過程中會在數據庫創建對應的數據表。
三、創建服務模型
服務模型以僞代碼的形式提供具體的業務邏輯服務,通過主菜單New->Service創建服務模型,並參考下圖輸入增刪改查的方法。同樣在操作完成後,點擊主菜單Models->Save保存當前模型,並且點擊Models->Publish發佈當前模型,發佈過程中會將僞代碼轉換爲真正的運行時代碼並編譯爲服務插件備用。
四、創建視圖模型
視圖模型有兩種形式:一種是拖拽方式生成json配置並渲染的界面,適用於快速配置如大屏頁面及簡單的增刪改查頁面;另一種是代碼的形式描述用戶界面,百分百靈活且經過編譯後運行性能高。這裏只介紹代碼形式,通過主菜單New->View新建視圖模型,新建對話框的類型選擇"Code"方式,參考以下代碼分別建立一個表單視圖及一個列表視圖,並且保存發佈。
- CustomerForm視圖
using sys.Entities;
namespace sys.Views;
public sealed class CustomerForm : View
{
public static Widget Preview() => new CustomerForm(
new Customer { Code = "", Name = "", Phone = "", Address = "" }
);
public CustomerForm(Customer obj)
{
Child = new Column
{
Spacing = 5,
Children =
{
new Text("客戶信息") { FontSize = 28 },
new Form
{
LabelWidth = 60,
Children =
{
new ("編號:", new TextInput(obj.Observe(c => c.Code))),
new ("名稱:", new TextInput(obj.Observe(c => c.Name))),
new ("電話:", new TextInput(obj.Observe(c => c.Phone))),
new ("地址:", new TextInput(obj.Observe(c => c.Address))),
}
},
new Container
{
Padding = EdgeInsets.Only(70, 0, 5, 0),
Child = new Button("保存", MaterialIcons.Save)
{
Width = float.MaxValue,
OnTap = _ => Save(obj),
}
},
}
};
}
private async void Save(Customer obj)
{
try
{
await sys.Services.CustomerService.Save(obj);
obj.AcceptChanges();
Notification.Success("保存成功!");
}
catch (Exception ex)
{
Notification.Error($"保存失敗: {ex.Message}");
}
}
}
- CustomerList視圖
using sys.Entities;
namespace sys.Views;
public sealed class CustomerList : View
{
public CustomerList()
{
Padding = EdgeInsets.All(10);
Child = new Column
{
Spacing = 10,
Children =
{
new Card { Padding = EdgeInsets.All(5), Child = BuildHeader() },
new Card { Child = BuildBody() }
}
};
}
private readonly State<string> _searchKey = "";
private readonly DataGridController<Customer> _dgController = new();
private Widget BuildHeader() => new Row
{
Height = 30,
Spacing = 10,
Children =
{
new Expanded(),
new TextInput(_searchKey) { Width = 100, Suffix = new Icon(MaterialIcons.Search) },
new Button("查詢") { OnTap = _ => Fetch() },
new ButtonGroup
{
Children =
{
new Button("新增") { OnTap = _ => OnCreate() },
new Button("編輯") { OnTap = _ => OnEdit() },
new Button("刪除") { OnTap = _ => OnDelete() }
}
}
}
};
private Widget BuildBody() => new Expanded(new DataGrid<Customer>(_dgController)
{
Columns =
{
new DataGridTextColumn<Customer>("編號", t => t.Code) { Width = 60 },
new DataGridTextColumn<Customer>("名稱", t => t.Name),
new DataGridGroupColumn<Customer>("聯繫方式")
{
Children =
{
new DataGridTextColumn<Customer>("電話", t => t.Phone),
new DataGridTextColumn<Customer>("地址", t => t.Address),
}
}
}
});
protected override void OnMounted() => Fetch();
private async void Fetch()
{
try
{
var list = await sys.Services.CustomerService.Fetch(_searchKey.Value);
_dgController.DataSource = list;
_dgController.TrySelectFirstRow();
}
catch (Exception ex)
{
Notification.Error($"查詢客戶列表失敗: {ex.Message}");
}
}
private void OnCreate() => Dialog.Show("新建客戶",
d => new CustomerForm(new Customer { Code = "", Name = "", Phone = "", Address = "" }
));
private void OnEdit()
{
var obj = _dgController.CurrentRow;
if (obj == null) return;
Dialog.Show("編輯客戶", d => new CustomerForm(obj));
}
private async void OnDelete()
{
var obj = _dgController.CurrentRow;
if (obj == null) return;
try
{
await sys.Services.CustomerService.Delete(obj);
Fetch();
}
catch (Exception ex)
{
Notification.Error($"刪除客戶失敗: {ex.Message}");
}
}
}
Tip1: 可以點擊視圖模型編輯器上方工具條的"Preview"按鈕實時預覽效果,也可以點擊左側工具欄的大綱按鈕查看預覽視圖的組件樹及其佈局,如下圖所示:
Tip2: 另外可以在代碼編輯器內光標位置右鍵菜單選擇"Goto Definition"跳轉至相應的模型定義內,如下動圖所示光標定位實體屬性然後跳轉至實體設計器內:
五、設置路由並生成應用
以上步驟完成後,我們需要修改HomePage視圖註冊客戶列表視圖的路由,先選擇HomePage視圖,然後主菜單Models->Checkout簽出待修改,添加如下圖高亮行所示代碼,修改HomePage視圖後同樣需要保存發佈,最後需要點擊主菜單Apps->BuildApp生成Web應用。
這樣我們就可以在瀏覽器地址欄直接輸入localhost:5000/#/customers
訪問客戶列表視圖,如下圖所示:
六、小結
作者個人能力實在有限,目前還有很多Bug待修復,還有工作流引擎及報表引擎待從舊版移植過來,如有問題請郵件聯繫或Github Issue,歡迎感興趣的小夥伴們加入共同完善,當然更歡迎贊助項目或給作者介紹工作(目前找工作中)。