iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Software Development

ERP 架構師筆記:定義驅動的框架設計系列 第 7

Day 7:FormLayout 與多前端畫面產生

  • 分享至 

  • xImage
  •  

Day 7:FormLayout 與多前端畫面產生

昨天那一層的產物是實體資料表,改一次要搬資料,還得先算要不要停機。今天回到畫面這一側,性質正好相反:畫面要改,改的是一份定義,不必更版;而且改一次,所有前端一起變。

案例訂單那張表單就是這樣:桌面、瀏覽器、iOS、Android 四個端讀同一份 FormLayout,各自長出自己的樣子,沒有一張畫面是手寫的。

本篇說明:

  1. FormLayout 的詞彙為何刻意簡化
  2. 從一個欄位到一個原生控件
  3. 各端各有一個渲染層
  4. 這一層之所以要獨立,是因為它會過期
  5. 窄螢幕變形為何不寫進定義
  6. 畫面只有自動排版與手動排版兩種,接上資料那一段兩種都一樣

一、FormLayout 的詞彙刻意簡化

一份 FormLayout 分成兩種區塊:

  • 主檔區塊:可以有好幾個,全部共用同一個欄數
  • 明細區塊:可以有好幾個,也可以一個都沒有,一律整寬排在主檔下面

主檔區塊裡放欄位,而版面對一個欄位只交代這幾樣:屬於哪一群、排在誰後面、要不要佔寬一點、用哪一種控件、顯不顯示、唯不唯讀、必不必填。明細區塊裡放的是欄,能交代的差不多同一組,多一個寬度。

版面不講位置。整份定義找不到一個座標,也沒有「放在第幾列第幾欄」這種話。位置是渲染層照順序流出來的:欄位一個接一個填,一列填滿就換下一列,標了跨兩欄的就佔兩格。連那個欄數都不是承諾,是上限,窄螢幕上會被壓下來。

案例訂單那份版面節選,完整檔案是 Order.FormLayout.xml

<LayoutSection Name="Main" Caption="Order">
  <Fields>
    <LayoutField FieldName="order_date" Caption="Order Date" />
    <LayoutField FieldName="customer_rowid" Caption="Customer" ControlType="ButtonEdit"
                 DisplayFields="ref_customer_id,ref_customer_name" />
    <LayoutField FieldName="total_amount" Caption="Total Amount" ReadOnly="true" />
  </Fields>
</LayoutSection>

一份版面只回答四個問題:這張表單有哪些欄位、什麼順序、分成哪幾群、每個欄位用哪一種控件。顯不顯示、唯不唯讀、必不必填是掛在欄位上的狀態,不改變形狀。

詞彙少是這一層的規格,不是它還沒長大。版面不是最終要畫出來的那一份,是中間那一份:它的下游不是某一種畫面,是各端各自的排版機制,而那些機制彼此沒有共同點(一邊是桌面的排版控件,一邊是瀏覽器的格線,下一代前端用什麼現在還不知道)。要餵給這些下游的中介,詞彙必須少到每一種都翻得動。

所以不在裡面的東西:

  • 擺放的事(對齊、邊界、間距)
  • 樣式、模板與動畫(那些是「畫成什麼樣子」,歸渲染層)
  • 區塊裡再放區塊(一份版面攤開來是平的,可以整批掃、可以程式化檢核)

少掉的東西比留下的重要:一個被釘在某個像素位置的欄位,換一個螢幕尺寸就壞了,而定義裡若寫得下這種話,壞掉的是上千份定義。

幾百張畫面長得一樣

ERP、HRM、CRM 的使用者幾乎都是公司內部成員。他們不是挑一張畫面來用,是被指派一整個模組,上線前要學會其中幾十張、幾百張表單。

畫面若一張張手寫,每張都可以長得不一樣:這張明細在右邊、那張在下面;這張挑客戶是點放大鏡開窗、那張是下拉選單;這張的新增在表格上方、那張在右下角。每處差異單獨看都不算什麼,湊起來就是使用者要記的東西。

版面的詞彙少,這些差異就沒有地方發生。使用者要學的不是幾百張畫面,是一張畫面加上幾百組欄位。上線訓練的成本因此不跟表單數量成正比,而教育訓練在導入預算裡從來不是小數目,換一批人員、加一個部門還要再付一次。

代價:換到一致,就等於每一張畫面都不會為它自己最佳化。某張使用頻率特別高的表單,就算自己去調那份版面,也只能在這組詞彙裡排。划不划算取決於張數:十張畫面各自最佳化是好事,幾百張各自最佳化就是幾百種操作習慣。


二、欄位對應控件

欄位若沒有特別指定控件,控件種類就由 FormSchema 上的型別決定:

FormSchema 宣告的型別 預設的控件種類 桌面端 網頁端
布林 CheckEdit 核取方塊控件 核取方塊輸入元素
日期、日期時間 DateEdit 日期輸入控件 日期輸入元素
長文字 MemoEdit 多行文字控件 多行文字元素
短整數、整數、長整數、小數、金額 NumericEdit 數值輸入控件 文字輸入元素
其餘 TextEdit 文字輸入控件 文字輸入元素

控件種類翻成各端的原生控件。上表右邊兩欄就是,兩邊除了名字之外沒有共同點。

挑控件還有一條規則比型別優先:欄位若宣告了跨表關連而沒有特別指定控件,預設就是開窗選取的編輯器。Day 5 說同一段關連宣告在畫面這一側也在做事,落點就是這裡。這個控件一次繫住好幾個欄位:值落在關連鍵上,畫面顯示的是它帶出來的那幾個欄位,選一次全部一起寫回,所以那些顯示欄不會再各自佔一格。

控件綁上去時回頭讀 FormSchema。文字控件去拿最大長度,下拉控件去拿選項清單,開窗選取的編輯器去拿它該開哪一支程式。版面因此只要記「用哪一種控件」,不必把欄位語意再說一次。

跟 UI 框架有關的只有翻成原生控件那一段,而它整個落在渲染層裡面。前後兩段只跟定義打交道,換一個端一個字都不必動。LanguageResource 管文字,FormLayout 管排法,ViewModel 處理資料繫結。


三、同一份 FormLayout,各端各有一個渲染層

版面這一層不知道有 UI 框架這回事,這句話可以驗證,方式跟 Day 4 那次一樣:定義層的引用清單極短,任何一家 UI 框架都不在裡面。

所以「翻成什麼」全部發生在各端自己的渲染層裡:

  • 欄數:版面上就是一個數字。桌面把它翻成排版控件上等寬的幾欄,網頁把它翻成瀏覽器格線排版的一段樣式宣告。
  • 清單:同一份清單版面,桌面交給表格控件去畫,網頁直接鋪成一張網頁表格。兩邊讀的欄位、順序、寬度是同一份。

案例的四個前端就是這樣組起來的:桌面、瀏覽器、iOS、Android 共用同一個 UI 專案,各平台入口只剩啟動與平台外殼那幾個檔案。

「寫一個渲染層」聽起來很虛,拆開來是四件事:

  1. 把兩種區塊排出來
  2. 把控件種類對到這一家的原生控件
  3. 把控件接上 ViewModel
  4. 決定窄螢幕上要怎麼變形

都不簡單,但都是有限的,而且做完之後這一端就能跑所有表單。

清單上沒有「怎麼跟後端溝通」,那一段不必重寫:每一個前端都經過同一層呼叫端程式,多一種前端框架,後端一行都不必跟著動(呼叫端那一層是 Day 17 的題目)。

四個端共用的那個渲染層就是 Bee.UI.Avalonia,連瀏覽器那一端也是同一套程式編譯過去的。多支援一個端要付的是一個渲染層,不是零。


四、這一層之所以要獨立,是因為它會過期

Day 1 那張對照表有一列是「換一代前端要付什麼」,今天兌現它。

ERP/HRM 套裝的產品週期十年、二十年起跳,這段時間裡系統得跟著前端技術的世代更替長出不同樣子。桌面程式、Web、行動 App,一代來的時候問題都是同一句:那幾百張畫面,要不要整批重做?

畫面若一張張手寫,答案只有重做,而且量跟表單數量成正比,跟業務有沒有變一點關係都沒有。一家公司的訂單十年來只多了兩個欄位,畫面卻已經被重寫過三次。

分層要解的就是這一題:結構穩定,跟著業務走;版面易變,跟著前端世代走。

兩者的節奏差在支出的形狀:

變動節奏 支出形狀
結構 連續 小額:一年多幾個欄位、改幾條規則,每次動一兩張表單
版面 間歇 大額:十年沒事,然後一次全換

手寫的畫面把兩種節奏綁在同一份檔案裡,那筆十年一次的大額支出因此每次都乘上表單總數;分開之後付的是一次固定成本。

這不是免費的。渲染層要有人寫、要寫對,而且它是一個收斂點:手寫畫面一次錯一張,渲染層一次錯全部。划不划算取決於表單有幾張,上千張的系統差距大到不用算,十張的系統自己寫一寫還比較快。

排好的版面檔會不會也過期?會。差別在它是資料:改一份資料,跟改一份畫面程式再重新編譯、打包、部署,不是同一件事。


五、窄螢幕自己變形,不必另外排一份

多數做法是為手機另外排一份,於是同一張表單有兩份版面,而兩份會各自演進。

框架的做法是讓渲染層自己變形:

  • 單筆畫面:主檔欄數壓成一欄;明細區塊從就地編輯改成點一列、開一張列編輯表單
  • 清單:寬的時候是一列一行的表格,窄的時候是一列一張卡片,每個欄位在卡片上佔一行,左標題右值
iOS Android
Northwind 訂單清單,iOS Northwind 訂單清單,Android

卡片上那幾個欄位不是另外列的一份清單。寬版表格與窄版卡片讀的是同一份清單版面,同一組欄、同一個順序,差別只在拿它去畫成什麼。

沒有兩份定義在互相同步,只有一份定義配兩種畫法,而這正是它最實際的價值:多一份就多一次對不起來的機會,而清單少一欄這種事通常要等使用者問「為什麼手機上看不到金額」才會被發現。

這些變形的決定,一個字都不寫在版面定義裡。版面說的是有哪些欄位、分成幾欄;「多窄算窄」「窄的時候明細要怎麼編」是渲染層自己的判斷。框架原始碼在這裡留了一句註解:明細怎麼編是 UI 層的事,共用的版面定義維持中立。

界線畫在這裡有理由:版面定義一旦開始描述「手機上要怎樣」,它就把一組關於螢幕的假設寫進了定義。現在的假設是手機與桌面兩種寬度,下一代可能是別的形狀,而寫進定義的假設要改就是改上千份。

代價:同一份版面在不同渲染層上不會長得完全一樣。這是刻意的。要求各端像素級一致,等於要求各端都放棄自己的平台慣例,而使用者對平台慣例的期待比對一致性的期待強得多。


六、標準訂完了,彈性在哪裡

Day 3 那個疑慮在這一層更尖銳,因為畫面是使用者天天看的東西。畫面只有自動排版與手動排版兩種,而接上資料那一段兩種都一樣。

方式 畫面怎麼來 適用
自動排版 FormLayout:版面在設計階段先產出一份,之後照需要調整:分幾個區塊、幾欄、哪個欄位跨兩欄、哪些不顯示、哪一欄唯讀、明細顯示哪幾欄。它是資料,改完不必重新編譯 制式表單,也就是絕大多數
手動排版 控件自己擺、位置自己決定,不經過版面那一層 版面那組詞彙排不出來的形狀

手動排版不是把框架關在門外。控件擺好之後填上資料表名與欄位名,值的讀寫、變更通知、最大長度與選項清單這些就照樣接得上。兩種方式的差別只在畫面是排出來的還是人手配置的,繫結那一段完全一樣。

版面那組詞彙不打算描述所有形狀。它不能,也不該:儀表板、圖表、拖拉式排程、地圖這些走的是手動排版。版面是「一套什麼都能做的框架等於什麼決定都沒替你做」這句話最容易被要求破例的地方:只要多加一種區塊、多開一個樣式屬性,就能多蓋住一種畫面,而每加一個,每一個渲染層要對付的東西就多一樣。

彈性的位置在顆粒度,不在能力。從自動排版到手動排版,中間沒有斷層,因為接資料那一段兩邊是同一套。


七、案例:訂單那份版面說了什麼、沒說什麼

案例的定義目錄裡有八份版面,一張表單一份,都是設計階段產出之後再調整出來的。前面節選過的訂單那一份說了這些:

  • 主檔一個區塊、兩欄、一個明細區塊
  • 主檔三個關連欄與明細那一欄,用的都是開窗選取的編輯器,帶出來的顯示欄不再各自佔一格
  • 金額那一欄在 FormSchema 上標了唯讀,產出版面時一起帶過來,畫面上就是不給人填
  • 四個端讀同一份,長出四個平台各自的樣子

訂單那份版面沒有說的事比說了的更多:沒說每個欄位最後落在畫面什麼位置、沒說開窗選取的編輯器長什麼樣、沒說手機上怎麼排、沒說標題要翻成哪一種語言、沒說明細在窄螢幕上怎麼編。正因為都不是它的職責,它才翻得到四個端上去。


那一天要重寫的東西

一份版面定義長出四個前端的畫面,這句話真正的內容不是「畫面也能用定義描述」,能描述只是入場條件。它的內容是三件事的總和:

  • 這一層的詞彙很少,少到每一端都翻得動,也少到幾百張表單長得一樣
  • 它不知道自己會被翻成什麼,所以多一個端就是多一個渲染層,而不是回頭改定義
  • 它是整套定義裡最容易過期的一份,而過期的時候被換掉的是渲染層,不是結構

十年後會出現什麼樣的前端我不知道,二十年前也沒有人猜得到現在會長成這樣。能先安排的只有一件事:讓那一天到來的時候,要重寫的東西是有限的,而且跟表單有幾張無關。

明天談算出來的欄位與存檔前的規則,能宣告到什麼程度。


本系列同步發表於 HackMD,完整目錄


上一篇
Day 6:TableSchema 與資料庫結構升級
下一篇
Day 8:宣告式業務規則與計算欄
系列文
ERP 架構師筆記:定義驅動的框架設計9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言