Day 19:第一個 Widget,把訂單數字擺上首頁 結尾留下一句提醒,統計數字類型的 Widget 讓管理者一眼看到當下的數字,但這個數字只是當下這個時間點的切片。本月訂單數如果是二十五筆,這二十五筆究竟是比上個月成長還是萎縮,光看這一個數字完全感受不到。
具體想像一下這個侷限在管理現場會造成什麼影響。管理者打開後台首頁,看到本月訂單數是二十五筆,這個數字本身不帶任何方向性。它可能是連續成長第三個月的其中一筆紀錄,也可能是剛從谷底回升的第一筆訊號,這兩種情況背後管理者該採取的因應動作完全不同,前者也許該考慮加開產線或增聘客服,後者則可能只是暫時止跌,還不到樂觀的時候。但統計數字本身不會告訴你正站在哪一種情境裡,它只回答了「現在是多少」,回答不了「這是怎麼走到這裡的」。
庫存的情況也是同樣的道理。某個分類的庫存數量看起來還算充足,但如果不知道這個數量是這兩週穩定消耗下來的結果,還是剛進完一批貨的暫時高點,管理者很難判斷該不該提早排下一批進貨。數字停留在原地,變化的過程卻消失了。
今天要做的事情,是在 Day 19 已定案的 Widget 機制之上,補上圖表這一種內容類型,把訂單與庫存資料隨時間變化的樣貌視覺化呈現,讓管理者看得出趨勢,而不只是看到當下的一個數字。
在動手之前,先把統計數字跟圖表這兩種 Widget 類型放在一起比較清楚,這一步能省掉後面很多困惑。直覺上很容易把圖表當成統計數字的另一種畫法,反正都是把資料庫裡的數字端到畫面上,差別只在有沒有畫成曲線。這個直覺低估了兩者的差異。統計數字回答的是「現在是多少」,圖表回答的是「這段時間以來是怎麼變化的」,兩者服務的管理情境不同,統計數字適合快速確認現況,例如打開手機看一眼現在幾點;圖表適合判斷方向,例如看一段時間的體重記錄才知道最近是瘦了還是胖了。統計數字是一張照片,圖表則像是連續播放的影片,影片才看得出動作的方向。
兩者需要的資料形態也完全不同,這是實作上真正的分水嶺。統計數字只需要一個聚合後的單一數值,Order::query()->count() 這類查詢跑出一個結果就結束了。圖表需要的是一系列依時間排列的數值點,例如過去七天,每一天各自對應一筆訂單數,或是過去六個月,每個月各自對應一筆訂單數。這種資料形態的整理,才是圖表 Widget 跟統計數字 Widget 在實作上最大的差異,前者查一次資料庫就夠,後者得把資料依時間分組,變成一組標籤搭配一組數值的陣列。
順帶回答一個常見的疑問,Filament 的圖表 Widget 是不是要自己刻一套繪圖邏輯。答案是不需要,Filament 通常整合現成的圖表函式庫負責實際繪製,開發者要做的主要工作是把資料整理成這個函式庫看得懂的格式,座標軸怎麼畫、線條怎麼描邊,這些前端繪圖細節早就被解決了,不需要重新發明一次。
先處理訂單趨勢這個示範。跟 Day 19 一樣,透過指令建立骨架,這次帶的參數不同:
php artisan make:filament-widget OrderTrendChart --chart
指令跑完,app/Filament/Widgets 資料夾裡會多出 OrderTrendChart.php,補上依日期分組統計訂單筆數的邏輯:
<?php
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Widgets\ChartWidget;
class OrderTrendChart extends ChartWidget
{
protected static ?string $heading = '近七天訂單趨勢';
protected function getData(): array
{
$days = collect(range(6, 0))->map(
fn (int $offset) => now()->subDays($offset)->startOfDay()
);
$counts = $days->map(function ($day) {
return Order::query()
->whereBetween('created_at', [$day, $day->copy()->endOfDay()])
->count();
});
return [
'datasets' => [
[
'label' => '訂單筆數',
'data' => $counts->toArray(),
],
],
'labels' => $days->map(fn ($day) => $day->format('n/j'))->toArray(),
];
}
protected function getType(): string
{
return 'line';
}
}
這段程式碼做的事情,是把「依日期分組計算訂單筆數」這個讀者原本就熟悉的聚合查詢寫法,重複跑七次,分別對應近七天的每一天,再把結果整理成 labels 對應 datasets 這種一組標籤搭配一組數值的陣列結構,這正是圖表函式庫看得懂的格式。created_at 沿用的是 Day 19 已經用過的訂單建立時間欄位,沒有引入任何新欄位。getType() 回傳 line,指定用折線圖呈現,訂單量隨天數推進的走勢,用折線最容易看出方向。
寫好之後,同樣放進 app/Filament/Widgets 資料夾,AdminPanelProvider.php 裡 Day 19 已經設定好的 discoverWidgets 會自動掃描到這個新類別,不需要額外註冊。
登入後台首頁驗證,本月訂單數與待處理訂單數這兩張卡片下方,多出一張折線圖,橫軸是近七天的日期,縱軸是當天的訂單筆數。

管理者看著這條線,不需要自己在腦中拼湊七個數字的變化,一眼就能看出這七天訂單量是持續往上、原地打轉,還是正在下滑,這正是本月訂單數這張卡片單獨存在時完全給不出的訊息。
訂單趨勢圖表回答的是「同一個指標隨時間怎麼變化」,接下來這個示範要處理另一種問題,「同一個時間點下,不同分類之間量體差多少」。庫存分佈圖表要呈現的正是這件事,依商品分類分組,統計各分類目前的庫存總量。
php artisan make:filament-widget InventoryByCategoryChart --chart
補上依分類分組加總庫存數量的邏輯:
<?php
namespace App\Filament\Widgets;
use App\Models\Product;
use Filament\Widgets\ChartWidget;
class InventoryByCategoryChart extends ChartWidget
{
protected static ?string $heading = '各分類庫存量';
protected function getData(): array
{
$stocks = Product::query()
->selectRaw('category, sum(stock) as total_stock')
->groupBy('category')
->pluck('total_stock', 'category');
return [
'datasets' => [
[
'label' => '庫存數量',
'data' => $stocks->values()->toArray(),
],
],
'labels' => $stocks->keys()->toArray(),
];
}
protected function getType(): string
{
return 'bar';
}
}
category 與 stock 都是商品模組在 Day 04:表單欄位全解析,從文字輸入到日期選擇器 就已經定案的既有欄位,這裡只是換一種分組聚合的方式,把它們依分類加總,整理成同一套 labels 對應 datasets 的格式。getType() 回傳 bar,用長條圖呈現,同一個時間點下,幾個貨架各自堆了多高,長條圖比折線圖更適合這種對比情境。
這裡要特別點出,這張圖表跟前一張訂單趨勢圖表雖然都屬於圖表類型,但回答的問題面向不一樣。訂單趨勢圖表看的是同一個指標在不同時間點各自的樣子,時間是橫軸的主角;庫存分佈圖表看的是同一個時間點下,不同分類各自的樣子,分類才是橫軸的主角。圖表類型本身不是單一用途的工具,會因為資料的分組維度不同,服務到不同的管理問題。
驗證方式跟前一個示範一樣,登入後台首頁,這次會看到第二張圖表出現,橫軸是各個商品分類,縱軸是該分類目前的庫存總量。

管理者掃過這排長條,哪個分類的庫存量偏低,需要盡快進貨,一眼就能看出來,不用切到商品列表頁逐筆核對數量再自己在心裡加總。這類「哪裡偏低該補貨」的洞察,是單一統計數字沒辦法傳遞的,得靠這種同時攤開多個分類的對比畫面才看得出來。
在 Day 19 已定案的 Widget 機制上,今天補上了訂單趨勢與庫存分佈這兩個圖表類型的 Widget。後台首頁現在同時具備統計數字與圖表兩種資訊區塊,管理者看得出現況,也看得出趨勢往哪裡走。
這裡也得誠實面對一個新出現的落差。不管是倉管、業務還是財務角色登入,看到的首頁都是同一套統計數字加圖表組合,訂單趨勢圖表呈現的永遠是全公司的整體資料。不同經銷據點的人登入,圖表裡的訂單走勢也還是同一份全公司彙總數字,沒有人看到專屬於自己據點的版本,這件事今天完全沒有處理。
回頭看看 Day 16:設計一份貼近真實團隊的角色權限矩陣 與 Day 17:多租戶隔離,讓每個經銷據點只看到自己的資料 已經建立的這兩層機制,角色權限矩陣決定了誰能操作哪些 Resource,多租戶隔離決定了每個據點只查得到自己的資料,這兩層機制目前都只作用在 Resource 的操作與資料查詢上,還沒有延伸到 Widget 這個今天才補上的呈現層。今天做出來的兩張圖表,暫時是所有登入者共用的同一套畫面。
誰登入看到誰的儀表板,Widget 排版與權限呼應,是接下來要處理的內容。