建立可探索的營運資料儀表板
專案情境
產品經理想知道:「本週哪個方案的啟用率下降最多?」這個問題決定了頁面需要時間範圍、方案篩選、比較值和明細,而不是任意放上幾種圖表。
1. 先定義查詢狀態
type DashboardQuery = {
range: '7d' | '30d' | '90d';
plans: string[];
};
const defaultQuery: DashboardQuery = {
range: '7d',
plans: [],
};
把查詢狀態放在 URL,使用者才能複製連結分享目前視圖:
const params = new URLSearchParams({ range: query.range });
query.plans.forEach((plan) => params.append('plan', plan));
history.replaceState(null, '', `?${params}`);
實際專案要同時處理瀏覽器返回、無效參數和沒有任何方案的狀態。
2. 依問題選擇圖表
| 使用者問題 | 主視覺 | 輔助內容 |
|---|---|---|
| 哪個方案下降最多? | 排序橫條圖 | 變化率表格 |
| 下降從何時開始? | 折線圖 | 日期範圍 |
| 哪些客戶受影響? | 表格 | 分頁與匯出 |
圖表提供趨勢,表格提供精確值。不要讓 tooltip 成為取得數字的唯一方式。
3. 處理更新中的畫面
function Results({ state }) {
if (state.status === 'loading' && !state.data) return <DashboardSkeleton />;
if (state.status === 'error') {
return <RetryPanel onRetry={state.retry}>資料載入失敗</RetryPanel>;
}
if (state.data?.length === 0) {
return <EmptyState action="清除篩選">沒有符合條件的資料</EmptyState>;
}
return <ChartAndTable data={state.data} />;
}
已有資料時重新查詢,不要整頁換成空白 skeleton;保留舊結果並在篩選器旁顯示「更新中」,能降低跳動和不確定感。
4. 寫出文字摘要
本週「專業版」啟用率下降 8.4%,是所有方案中降幅最大者。
點擊方案名稱可查看受影響客戶。
這段摘要同時服務無法看圖表的使用者,也幫助所有人快速理解頁面結論。
驗收
- 篩選條件顯示在畫面上且可由 URL 恢復。
- 查詢期間保留上下文,不會把焦點移回頁面頂端。
- 圖表和表格的數字、單位與時間範圍一致。
- 鍵盤可以操作篩選器和結果連結。
延伸練習
新增「與上一期間比較」功能,處理沒有上一期間資料、資料延遲和負值百分比三種情況。