iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Modern Web

Laravel Filament 從入門到實戰系列 第 22 篇

Day 21:誰登入看到誰的儀表板,Widget 排版與權限呼應

  • 分享至 

  • xImage
  •  

Day 20:圖表 Widget,讓庫存與訂單趨勢一眼看懂 結尾留下一句話,誰登入看到誰的儀表板,Widget 排版與權限呼應,是接下來要處理的內容。

今天要處理的正是這句話,也是第六階段「Widget 與客製化儀表板」的最後一天。

首頁看起來很豐富,但豐富得不太對勁

打開目前這套後台的首頁,畫面確實比十天前豐富許多。本月訂單數與待處理訂單數這兩張卡片並排在最上方,下方接著訂單趨勢的折線圖跟各分類庫存量的長條圖,數字有了,趨勢也看得出來。但這份豐富藏著一個問題,不管是倉管、業務還是財務角色登入,看到的都是同一套組合,四個 Widget 一個不少地全部顯示,訂單趨勢圖表呈現的也永遠是全公司彙總後的資料。角色權限矩陣跟多租戶隔離這兩層機制,目前都只作用在 Resource 的操作按鈕跟查詢範圍上,還沒有延伸到 Widget 這個新的呈現層。

把這個落差攤開來看會更清楚它有多站不住腳。倉管人員每天在意的是庫存夠不夠、該不該補貨,這是 Day 16:設計一份貼近真實團隊的角色權限矩陣 已經畫清楚的職責範圍,跟公司整體營收沒有直接關係。但今天的首頁上,倉管一登入,本月訂單數卡片就攤在眼前,如果哪天再加一張營收類的統計數字,倉管同樣會看得一清二楚,明明這是財務角色才該關心的資訊。據點這邊的問題更直接,台北據點的業務打開訂單趨勢圖表,那條折線裡其實混雜了高雄據點的訂單量,這正好違背 Day 17:多租戶隔離,讓每個經銷據點只看到自己的資料 辛苦建立起來的據點邊界,一個業務理應只看得到自己據點的資料,圖表卻悄悄把這道邊界繞過去了。

今天要做的事情,是讓 Widget 的顯示邏輯與排版配置,正式接上 Day 15 到 Day 18 已經建立的 Policy 整合、角色權限矩陣、多租戶隔離這幾層機制。Widget 不是憑空長出來、獨立於權限之外的新功能,接下來會證明它其實是接在既有機制上運作的延伸。不新增任何新的 Widget 類型,倉管、業務、財務三個角色,台北、高雄兩個經銷據點,統計數字跟圖表這兩種既有的 Widget,是今天示範完全依循的範圍。

Widget 也該問 Policy,這個人看得到這塊資訊嗎

Day 15:接上 Laravel 既有的 Policy,誰能看見這個操作按鈕 定案的機制,讓 Filament Resource 自動讀取 Policy 判斷是否顯示操作按鈕。今天要示範的是同一套精神延伸到 Widget 上,只是 Widget 沒有現成的自動判斷機制,得自己在 Widget 類別裡加上一段 canView(),回傳的判斷依據直接借用既有的角色矩陣邏輯,不重新設計一套獨立規則。

先處理 OrderStatsOverview 這個 Day 19 建立的統計數字 Widget。目前這張卡片對所有角色一視同仁,加上一段顯示條件,讓它只在財務或業務角色登入時出現,倉管角色不該一打開首頁就看到訂單數字這類跟庫存無關的資訊:

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class OrderStatsOverview extends BaseWidget
{
    public static function canView(): bool
    {
        return in_array(auth()->user()->role, ['sales', 'finance']);
    }

    protected function getStats(): array
    {
        // 統計邏輯沿用 Day 19 定案內容,不重複列出
    }
}

role 這個欄位跟 warehouse、sales、finance 這三種值域,都是 Day 16 畫矩陣時已經定案的既有設計,這裡沒有引入任何新概念,只是把原本寫進 ProductPolicy、OrderPolicy 裡的角色判斷句子,搬來 Widget 類別裡問一次一樣的問題。

接著處理 [[Day 20:圖表 Widget,讓庫存與訂單趨勢一眼看懂]] 建立的兩張圖表 Widget。庫存分佈圖表 InventoryByCategoryChart 加上顯示條件,只在倉管角色登入時出現,業務跟財務平常不需要盯著各分類的庫存長條圖過日子:

public static function canView(): bool
{
    return auth()->user()->role === 'warehouse';
}

訂單趨勢圖表 OrderTrendChart 則維持對業務跟財務都開放,兩個角色都需要掌握訂單量的走勢,只是關注的角度不同,業務看重的是量體變化以判斷接單節奏,財務看重的則是趨勢跟營收目標之間的落差,這層角度上的差異留在圖表標題或說明文字上稍微點出即可,不需要為此另外拆出兩個 Widget 類別:

public static function canView(): bool
{
    return in_array(auth()->user()->role, ['sales', 'finance']);
}

這幾段程式碼加起來,做的事情其實非常單純,canView() 裡的判斷句子,跟 Day 16 寫進 ProductPolicy、OrderItemPolicy 裡的角色判斷,骨子裡是同一件事,只是問的問題從「這個人能不能編輯這筆資料」換成「這個人看不看得到這塊資訊」。Policy 整合這套機制,同時撐起了角色權限的操作判斷,也撐起了今天 Widget 的顯示判斷,兩者共用一套角色邏輯,Widget 沒有另外發明一套權限規則,只是換了個地方問而已。

登入驗證一次,用倉管帳號打開首頁,只看得到庫存分佈長條圖,本月訂單數卡片跟訂單趨勢折線圖都不見了。換成業務帳號,訂單相關的統計數字跟趨勢圖表都出現,庫存長條圖則消失。財務帳號登入的結果跟業務類似,都看得到訂單相關的兩個 Widget,庫存分佈圖表同樣不會出現。三種角色,第一次在 Widget 這個新的呈現層上,也擁有各自不同的畫面。

Widget 的數字也該記得自己是哪個據點的

角色維度的顯示差異處理完,接下來輪到據點維度的資料差異。這一步反而不需要新寫太多程式碼,得先確認的是一件更基本的事,OrderStatsOverview 跟 OrderTrendChart 背後查詢訂單資料的邏輯,到底有沒有自動套用 Day 17:多租戶隔離,讓每個經銷據點只看到自己的資料 建立的 BranchScope。

回頭看 Day 17 掛載 BranchScope 的方式,這個 Global Scope 是直接掛在 Order 模型的 booted() 方法裡,代表任何地方對 Order::query() 發出的查詢,都會自動被限制在目前登入使用者的據點範圍內,不管這段查詢是寫在 Resource 的列表頁,還是寫在 Widget 類別的 getStats() 或 getData() 裡。OrderStatsOverview 裡計算本月訂單數的那句 Order::query()->whereBetween(...)->count(),跟 OrderTrendChart 裡依日期分組計算訂單筆數的查詢,都是直接呼叫 Order::query(),這代表兩個 Widget 背後的查詢,理論上已經自動繼承了這層據點限制,不需要另外在 Widget 裡多寫一句 where('branch_id', ...)。這正是 Day 17 選擇用 Global Scope 而不是在每個查詢點手動加條件的價值所在,新增一個查詢的地方,不代表要重新記得加一次隔離邏輯。

理論歸理論,實際操作驗證一次才算數。準備兩組帳號,都是業務角色,一組 branch_id 指向台北據點,一組指向高雄據點,確保這次看到的差異純粹來自據點隔離,不是角色矩陣造成的。

用台北據點的業務帳號登入,首頁上的本月訂單數只計入台北的訂單,訂單趨勢折線圖那條線的起伏,也只反映台北據點近七天的訂單量。登出換成高雄據點的業務帳號,本月訂單數變成完全不同的數字,訂單趨勢折線圖也換了一條線,兩邊的數字跟線條都對得上各自據點實際的訂單狀況,彼此不會看到對方據點的內容。

台北據點與高雄據點業務帳號登入後的 Widget 對照,示意兩者的統計數字卡片與訂單趨勢折線圖彼此不同

這次驗證確認了一件事,多租戶隔離一旦透過 Global Scope 這種方式實作,後續任何涉及資料查詢的新畫面,都能預設已經套用這層隔離,不需要每加一個新地方就重新設一次條件。今天在 Widget 這個新的呈現層上,這件事第一次被實際檢驗,結果確實成立,不需要重新論證多租戶隔離本身怎麼運作,只需要確認它真的延伸到了新的地方。

排版也該因人而異,把最重要的資訊放在最顯眼的位置

顯示與否處理完,還有一層問題前面兩段都沒碰到,這個 Widget 如果會出現,該排在首頁的哪個位置、佔多大版面。這是跟前兩段不同層次的問題,前面處理的是「這個 Widget 該不該出現」,這一段處理的是「出現的話該排在哪裡」,兩者合起來才是完整的「誰登入看到誰的儀表板」。

Filament 提供兩個屬性控制這件事,$sort 決定 Widget 在頁面上排列的先後順序,數字越小排得越前面;$columnSpan 決定 Widget 佔用版面的欄數或寬度。這兩個屬性都可以動態決定,不必是寫死的固定值。

以財務角色為例,財務關心的是訂單金額,訂單相關的統計數字理應排在最顯眼、最上方的位置,也值得佔用比較大的版面:

class OrderStatsOverview extends BaseWidget
{
    public function getColumnSpan(): int | string | array
    {
        return auth()->user()->role === 'finance' ? 'full' : 2;
    }

    protected function getSort(): int
    {
        return auth()->user()->role === 'finance' ? 1 : 2;
    }
}

倉管角色的關注重點相反,庫存分佈圖表才是該擺在第一順位的資訊,InventoryByCategoryChart 也用同樣的方式調整:

class InventoryByCategoryChart extends ChartWidget
{
    protected function getSort(): int
    {
        return auth()->user()->role === 'warehouse' ? 1 : 3;
    }
}

登入驗證一次,財務帳號打開首頁,訂單統計數字卡片佔滿整排版面,排在最上方,訂單趨勢圖表接在下方。倉管帳號打開首頁,第一眼看到的變成庫存分佈長條圖,因為對倉管而言其他 Widget 本來就因為 canView() 的判斷而不會出現,庫存分佈圖表自然而然就成了首頁上唯一、也最顯眼的內容。同一套 Widget 集合,同一套 Panel 首頁,三個角色登入後,排列順序、版面大小、甚至能不能看到,全部因為身分不同而不同。

這一步暫時還沒打通,儀表板還能更貼近每個人一點

今天做的呼應仍然只是基本程度,值得誠實記一筆。今天示範的是角色跟 Widget 之間幾個具代表性的對應關係,倉管看庫存、業務跟財務看訂單,實際團隊裡同一個角色底下可能還有更細緻的個人化需求,比如同樣是業務,某個人可能更想把訂單趨勢圖表排在統計數字前面,這種同角色、不同個人的客製化,今天完全沒有處理。

這個落差留給儀表板持續打磨後理應深化的方向。目前做到的只是「依角色分流」,更進一步的「依個人偏好調整」留待後續視需要處理,這裡不展開任何具體實作方向。

這三天,後台第一次有了一張臉

今天是第六階段的收尾篇,適合完整回顧這三天走過的路徑。Day 19:第一個 Widget,把訂單數字擺上首頁 定案 Widget 這個錨點,在首頁建立本月訂單數與待處理訂單數這兩個統計數字,把原本得逐一點開列表頁才拼湊得出來的現況,第一次濃縮成幾張卡片。Day 20:圖表 Widget,讓庫存與訂單趨勢一眼看懂 補上訂單趨勢與庫存分佈這兩個圖表,讓管理者看得出走勢,而不只是當下的一個數字。今天把這四個 Widget,接上 Day 15 到 Day 18 已建立的 Policy 整合、角色權限矩陣、多租戶隔離,並處理排版配置,讓每個人登入看到的儀表板真正因應自己的身分而不同。

把這個階段前後對照一下,落差相當具體。階段開始之前,後台首頁是一片空白,管理者得逐一點開商品、訂單這幾個 Resource,才能一點一點拼湊出目前的現況。階段結束的今天,首頁變成一張真正因人而異的臉,倉管一登入,第一眼看到的是庫存分佈;業務跟財務一登入,第一眼看到的是佔滿版面的訂單統計數字跟趨勢;不同據點的人打開同一張圖表,看到的也是各自據點真實的訂單樣貌。這裡疊加起來的,不只是多做出來的一個功能,更是把第四階段驗證與生命週期、第五階段多角色權限與多租戶、第六階段 Widget 與儀表板,這三個此前各自獨立發展的階段,第一次匯流在同一個畫面上。驗證跟生命週期讓資料本身站得住腳,權限跟多租戶讓身分與邊界站得住腳,Widget 則是把前面兩個階段的成果,變成管理者第一眼就能感受到的具體畫面,三者缺一不可,少了任何一段,今天這張因人而異的首頁都做不出來。

這條路徑同樣呼應系列一直在講的那條主線,陽春後台逐步加深。第一天做出來的商品 Resource,只是一套五分鐘生出來的陽春 CRUD 介面,走到今天,同一套系統已經是一套具備身分邊界、也具備一眼看懂現況能力的完整後台雛形,而且這一切都疊加在同一個示範專案上,沒有哪一天是打掉重練的。

畫面齊了,資料量呢

功能、驗證、權限、儀表板,這套後台目前已經算得上齊備。但回頭看看這幾天示範操作時用的資料筆數,訂單頂多幾十筆,商品分類也頂多幾十個,資料量始終不大。這幾天示範起來順暢的表格、圖表、Widget 查詢,是建立在這個小規模的前提上,一旦商品、訂單、客戶的資料量真正成長到貼近實際批發商的營運規模,這套系統的操作是否還能維持順暢,是還沒有被檢驗過的問題。

資料變多之後,表格為什麼開始變慢,是接下來要處理的內容。


上一篇
Day 20:圖表 Widget,讓庫存與訂單趨勢一眼看懂
系列文
Laravel Filament 從入門到實戰 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言