- 實作 InventoryAnalysisController 與 TurnoverService - 新增庫存分析前端頁面 (Inventory/Analysis/Index.tsx) - 整合路由與選單 - 統一分頁邏輯與狀態顯示 - 更新 UI Consistency Skill 文件
37 KiB
name, description
| name | description |
|---|---|
| 客戶端後台 UI 統一規範 | 確保 Star ERP 客戶端(租戶端)後台所有頁面的 UI 元件保持統一的樣式與行為 |
客戶端後台 UI 統一規範
概述
本技能提供 Star ERP 系統客戶端(租戶端)後台的 UI 統一性規範,確保所有頁面使用一致的元件、樣式類別、圖標和佈局模式。
適用範圍:本規範適用於租戶端後台(使用
AuthenticatedLayout的頁面),不適用於中央管理後台(LandlordLayout)。
核心原則
- 使用統一的 UI 組件庫:優先使用
@/Components/ui/中的 47 個元件 - 遵循既定的樣式類別:使用
app.css中定義的自定義按鈕類別 - 統一的圖標系統:全面使用
lucide-react圖標 - 一致的佈局模式:表格、分頁、操作按鈕等保持相同結構
- 權限控制:所有操作按鈕必須使用
<Can>元件包裹
1. 專案結構
1.1 關鍵目錄
resources/
├── css/
│ └── app.css # 全域樣式與設計 Token
├── js/
│ ├── Components/
│ │ ├── ui/ # 47 個基礎 UI 元件 (shadcn/ui)
│ │ ├── shared/ # 共用業務元件 (Pagination, BreadcrumbNav 等)
│ │ └── Permission/ # 權限控制元件 (Can, HasRole, CanAll)
│ ├── Layouts/
│ │ ├── AuthenticatedLayout.tsx # 客戶端後台佈局 ⬅️ 本規範適用
│ │ └── LandlordLayout.tsx # 中央管理後台佈局
│ └── Pages/ # 頁面元件
1.2 可用 UI 元件清單
accordion, alert, alert-dialog, avatar, badge, breadcrumb, button,
calendar, card, carousel, chart, checkbox, collapsible, command,
context-menu, dialog, drawer, dropdown-menu, form, hover-card,
input, input-otp, label, menubar, navigation-menu, pagination,
popover, progress, radio-group, resizable, scroll-area,
searchable-select, select, separator, sheet, sidebar, skeleton,
slider, sonner, switch, table, tabs, textarea, toggle, toggle-group,
tooltip
2. 色彩系統
2.1 主題色 (Primary) - 動態租戶品牌色
注意:主題色會根據租戶設定(Branding)動態改變,嚴禁在程式碼中 Hardcode 色碼(如
#01ab83)。 請務必使用 Tailwind Utility Class 或 CSS 變數。
| Tailwind Class | CSS Variable | 說明 |
|---|---|---|
*-primary-main |
--primary-main |
主色:與租戶設定一致(預設綠色),用於主要按鈕、連結、強調文字 |
*-primary-dark |
--primary-dark |
深色:系統自動計算,用於 Hover 狀態 |
*-primary-light |
--primary-light |
淺色:系統自動計算,用於次要強調 |
*-primary-lightest |
--primary-lightest |
最淺色:系統自動計算,用於背景底色、Active 狀態 |
運作機制:
AuthenticatedLayout 會根據後端回傳的 branding 資料,自動注入 CSS 變數覆寫預設值。
// ✅ 正確:使用 Tailwind Class
<div className="text-primary-main">...</div>
// ✅ 正確:使用 CSS 變數 (自定義樣式時)
<div style={{ borderColor: 'var(--primary-main)' }}>...</div>
// ❌ 錯誤:寫死色碼 (會導致租戶無法換色)
<div className="text-[#01ab83]">...</div>
2.2 灰階 (Grey Scale)
--grey-0: #1a1a1a; /* 深黑 - 標題文字 */
--grey-1: #4a4a4a; /* 深灰 - 主要內文 */
--grey-2: #6b6b6b; /* 中灰 - 次要內文、Placeholder */
--grey-3: #9e9e9e; /* 淺灰 - 禁用文字、輔助說明 */
--grey-4: #e0e0e0; /* 極淺灰 - 邊框、分隔線 */
--grey-5: #fff; /* 白色 - 背景、按鈕文字 */
2.3 狀態色 (State Colors)
--other-success: #01ab83; /* 成功 - 同主題色 */
--other-error: #dc2626; /* 錯誤 - 刪除、警示 */
--other-warning: #f59e0b; /* 警告 - 提醒、注意 */
--other-info: #3b82f6; /* 資訊 - 說明、提示 */
3. 按鈕規範
3.1 按鈕樣式類別
專案在 resources/css/app.css 中定義了統一的按鈕樣式,必須使用這些類別:
Filled 按鈕(實心按鈕)— 用於主要操作
// ✅ 主要操作按鈕(綠色主題色)- 新增、儲存、確認
<Button className="button-filled-primary">
<Plus className="h-4 w-4 mr-2" />
新增項目
</Button>
// ✅ 成功操作
<Button className="button-filled-success">確認</Button>
// ✅ 資訊操作(用於系統提示、說明等非業務主流程)
<Button className="button-filled-info">系統資訊</Button>
// ✅ 警告操作
<Button className="button-filled-warning">警告</Button>
// ✅ 錯誤/刪除操作(AlertDialog 內確認按鈕)
<Button className="button-filled-error">刪除</Button>
Outlined 按鈕(邊框按鈕)— 用於次要操作
// ✅ 編輯按鈕(表格操作列)
<Button variant="outline" size="sm" className="button-outlined-primary">
<Pencil className="h-4 w-4" />
</Button>
// ✅ 刪除按鈕(表格操作列)
<Button variant="outline" size="sm" className="button-outlined-error">
<Trash2 className="h-4 w-4" />
</Button>
Text 按鈕(文字按鈕)
<Button className="button-text-primary">查看更多</Button>
3.2 按鈕大小
| Size | 高度 | 使用情境 |
|---|---|---|
size="sm" |
h-8 | 表格操作列、緊湊佈局 |
size="default" |
h-9 | 一般操作、表單提交 |
size="lg" |
h-10 | 主要 CTA、頁面主操作 |
size="icon" |
9×9 | 純圖標按鈕 |
3.3 常見操作按鈕模式
頁面頂部新增按鈕
<Can permission="resource.create">
<Link href={route('resource.create')}>
<Button className="button-filled-primary">
<Plus className="h-4 w-4 mr-2" />
新增XXX
</Button>
</Link>
</Can>
表格操作列檢視按鈕
<Can permission="resource.view">
<Link href={route('resource.show', item.id)}>
<Button
variant="outline"
size="sm"
className="button-outlined-primary"
title="檢視"
>
<Eye className="h-4 w-4" />
</Button>
</Link>
</Can>
表格操作列編輯按鈕
<Can permission="resource.edit">
<Link href={route('resource.edit', item.id)}>
<Button
variant="outline"
size="sm"
className="button-outlined-primary"
title="編輯"
>
<Pencil className="h-4 w-4" />
</Button>
</Link>
</Can>
表格操作列刪除按鈕(帶確認對話框)
<Can permission="resource.delete">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="outline"
size="sm"
className="button-outlined-error"
title="刪除"
>
<Trash2 className="h-4 w-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>確認刪除</AlertDialogTitle>
<AlertDialogDescription>
確定要刪除「{item.name}」嗎?此操作無法復原。
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(item.id)}
className="bg-red-600 hover:bg-red-700"
>
刪除
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Can>
3.4 返回按鈕規範
詳情頁面(如:查看庫存、進貨單詳情)的返回按鈕應統一放置於 頁面標題上方,並採用「圖標 + 文字」的 Outlined 樣式。
樣式規格:
- 位置:標題區域上方 (
mb-6),獨立於標題列 - 樣式:
variant="outline"+className="gap-2 button-outlined-primary" - 圖標:
<ArrowLeft className="h-4 w-4" /> - 文字:清楚說明返回目的地,例如「返回倉庫管理」、「返回列表」
<div className="mb-6">
<Link href={route('resource.index')}>
<Button
variant="outline"
className="gap-2 button-outlined-primary"
>
<ArrowLeft className="h-4 w-4" />
返回列表
</Button>
</Link>
</div>
3.5 頁面佈局規範(新增/編輯頁面)
標準結構
新增/編輯頁面(如:商品新增、採購單建立)應遵循以下標準結構:
<AuthenticatedLayout breadcrumbs={...}>
<Head title="..." />
<div className="container mx-auto p-6 max-w-7xl">
{/* Header */}
<div className="mb-6">
{/* 返回按鈕 */}
<Link href={route('resource.index')}>
<Button
variant="outline"
className="gap-2 button-outlined-primary mb-4"
>
<ArrowLeft className="h-4 w-4" />
返回列表
</Button>
</Link>
{/* 頁面標題區塊 */}
<div className="mb-4">
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Icon className="h-6 w-6 text-primary-main" />
頁面標題
</h1>
<p className="text-gray-500 mt-1">
頁面說明文字
</p>
</div>
</div>
{/* 表單或內容區塊 */}
<FormComponent ... />
</div>
</AuthenticatedLayout>
關鍵規範
- 外層容器:使用
className="container mx-auto p-6 max-w-7xl"確保寬度與間距一致 - Header 包裹:使用
<div className="mb-6">包裹返回按鈕與標題區塊 - 返回按鈕:加上
mb-4與標題區塊分隔 - 標題區塊:使用
<div className="mb-4">包裹 h1 和 p 標籤 - 標題樣式:
text-2xl font-bold text-grey-0 flex items-center gap-2 - 說明文字:
text-gray-500 mt-1
範例頁面
- ✅
/resources/js/Pages/PurchaseOrder/Create.tsx(建立採購單) - ✅
/resources/js/Pages/Product/Create.tsx(新增商品) - ✅
/resources/js/Pages/Product/Edit.tsx(編輯商品)
4. 圖標規範
4.1 統一使用 lucide-react
統一使用 lucide-react,禁止使用其他圖標庫(如 FontAwesome、Material Icons、react-icons 等)。
4.2 圖標尺寸標準
| 尺寸 | 類別 | 使用情境 |
|---|---|---|
| 小型 | h-3 w-3 |
Badge 內、小文字旁 |
| 標準 | h-4 w-4 |
按鈕內、表格操作 |
| 標題 | h-5 w-5 |
側邊欄選單 |
| 大型 | h-6 w-6 |
頁面標題 |
4.3 常用操作圖標映射
| 操作 | 圖標組件 | 使用情境 |
|---|---|---|
| 新增 | <Plus /> |
新增按鈕 |
| 編輯 | <Pencil /> |
編輯按鈕 |
| 刪除 | <Trash2 /> |
刪除按鈕 |
| 查看 | <Eye /> |
查看詳情 |
| 搜尋 | <Search /> |
搜尋欄位 |
| 篩選 | <Filter /> |
篩選功能 |
| 下載 | <Download /> |
下載/匯出 |
| 上傳 | <Upload /> |
上傳/匯入 |
| 設定 | <Settings /> |
設定功能 |
| 複製 | <Copy /> |
複製內容 |
| 郵件 | <Mail /> |
Email 顯示 |
| 使用者 | <Users />, <User /> |
使用者管理 |
| 權限 | <Shield /> |
角色/權限 |
| 排序 | <ArrowUpDown />, <ArrowUp />, <ArrowDown /> |
表格排序 |
| 儀表板 | <LayoutDashboard /> |
首頁/總覽 |
| 商品 | <Package /> |
商品管理 |
| 倉庫 | <Warehouse /> |
倉庫管理 |
| 廠商 | <Truck />, <Contact2 /> |
廠商管理 |
| 採購 | <ShoppingCart /> |
採購管理 |
4.4 圖標使用範例
import { Plus, Pencil, Trash2, Users } from 'lucide-react';
// 頁面標題
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<Users className="h-6 w-6 text-[#01ab83]" />
使用者管理
</h1>
// 按鈕內圖標(圖標在左,帶文字)
<Button className="button-filled-primary">
<Plus className="h-4 w-4 mr-2" />
新增使用者
</Button>
// 純圖標按鈕(表格操作列)
<Button variant="outline" size="sm" className="button-outlined-primary">
<Pencil className="h-4 w-4" />
</Button>
5. 表格規範
5.1 表格容器
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<Table>
{/* 表格內容 */}
</Table>
</div>
5.2 表格標題列
<TableHeader className="bg-gray-50">
<TableRow>
<TableHead className="w-[50px] text-center">#</TableHead>
<TableHead>名稱</TableHead>
<TableHead className="text-center">操作</TableHead>
</TableRow>
</TableHeader>
關鍵要點:
- 使用
bg-gray-50背景色 - 序號欄位固定寬度
w-[50px]並置中 - 操作欄位置中顯示
5.3 表格主體
<TableBody>
{items.length === 0 ? (
<TableRow>
<TableCell colSpan={5} className="text-center py-8 text-gray-500">
無符合條件的資料
</TableCell>
</TableRow>
) : (
items.map((item, index) => (
<TableRow key={item.id}>
<TableCell className="text-gray-500 font-medium text-center">
{startIndex + index}
</TableCell>
{/* 其他欄位 */}
<TableCell className="text-center">
<div className="flex items-center justify-center gap-2">
{/* 操作按鈕 */}
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
關鍵要點:
- 空狀態訊息使用置中、灰色文字
- 序號欄使用
text-gray-500 font-medium text-center - 操作欄使用
flex items-center justify-center gap-2排列按鈕
5.4 欄位排序規範
當表格需要支援排序時,請遵循以下模式:
- 圖標邏輯:
- 未排序:
ArrowUpDown(class:text-muted-foreground) - 升冪 (asc):
ArrowUp(class:text-primary) - 降冪 (desc):
ArrowDown(class:text-primary)
- 未排序:
- 結構:在
TableHead內使用button元素。 - 後端配合:後端 Controller 必須 處理
sort_by與sort_order參數。
// 1. 定義 Helper Component (在元件內部)
const SortIcon = ({ field }: { field: string }) => {
if (filters.sort_by !== field) {
return <ArrowUpDown className="h-4 w-4 text-muted-foreground ml-1" />;
}
if (filters.sort_order === "asc") {
return <ArrowUp className="h-4 w-4 text-primary ml-1" />;
}
return <ArrowDown className="h-4 w-4 text-primary ml-1" />;
};
// 2. 表格標題應用
<TableHead>
<button
onClick={() => handleSort('created_at')}
className="flex items-center hover:text-gray-900"
>
建立時間 <SortIcon field="created_at" />
</button>
</TableHead>
// 3. 排序處理函式 (三態切換:未排序 -> 升冪 -> 降冪 -> 未排序)
const handleSort = (field: string) => {
let newSortBy: string | undefined = field;
let newSortOrder: 'asc' | 'desc' | undefined = 'asc';
if (filters.sort_by === field) {
if (filters.sort_order === 'asc') {
newSortOrder = 'desc';
} else {
// desc -> reset (回到預設排序)
newSortBy = undefined;
newSortOrder = undefined;
}
}
router.get(
route(route().current()!),
{ ...filters, sort_by: newSortBy, sort_order: newSortOrder },
{ preserveState: true, replace: true }
);
};
6. 分頁規範
6.1 統一分頁元件
使用 @/Components/shared/Pagination 元件:
import Pagination from "@/Components/shared/Pagination";
import { SearchableSelect } from "@/Components/ui/searchable-select";
// 在表格下方(底部工具列)
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm text-gray-500">
<span>每頁顯示</span>
<SearchableSelect
value={perPage}
onValueChange={handlePerPageChange}
options={[
{ label: "10", value: "10" },
{ label: "20", value: "20" },
{ label: "50", value: "50" },
{ label: "100", value: "100" }
]}
className="w-[90px] h-8" // ✅ 統一使用 90px 寬度,避免「100」顯示不全
showSearch={false}
/>
<span>筆</span>
</div>
{/* 總筆數顯示:統一放在每頁顯示右側,使用 text-gray-500 */}
<span className="text-sm text-gray-500">共 {data.total} 筆紀錄</span>
</div>
<Pagination links={data.links} />
</div>
6.2 每頁筆數狀態管理
const [perPage, setPerPage] = useState<string>(filters.per_page || "10");
const handlePerPageChange = (value: string) => {
setPerPage(value);
router.get(
route('resource.index'),
{ per_page: value },
{ preserveState: false, replace: true, preserveScroll: true }
);
};
7. Badge 與狀態顯示
7.1 基本 Badge
import { Badge } from "@/Components/ui/badge";
// Outline 樣式(最常用)
<Badge variant="outline">{item.category?.name || '-'}</Badge>
// 預設樣式(主題色背景)
<Badge variant="default">啟用中</Badge>
// 錯誤樣式
<Badge variant="destructive">停用</Badge>
7.2 角色顯示(特殊樣式)
<div className="flex flex-wrap gap-2">
{user.roles.map(role => (
<div
key={role.id}
className={cn(
"inline-flex items-center px-2.5 py-1 rounded-md border",
role.name === 'super-admin'
? "bg-purple-50 border-purple-200"
: "bg-gray-50 border-gray-200"
)}
>
<div className="flex items-center gap-1.5">
{role.name === 'super-admin' && <Shield className="h-3.5 w-3.5 text-purple-600" />}
<span className={cn(
"text-sm font-medium",
role.name === 'super-admin' ? "text-purple-700" : "text-gray-900"
)}>
{role.display_name}
</span>
</div>
</div>
))}
</div>
7.3 統一狀態標籤 (StatusBadge)
系統提供統一的 StatusBadge 元件來顯示各種業務狀態,確保顏色與樣式的一致性。
引入方式:
import { StatusBadge, StatusVariant } from "@/Components/shared/StatusBadge";
支援的變體 (Variant):
| Variant | 顏色 | 適用情境 |
|---|---|---|
neutral |
灰色 | 草稿、取消、關閉、缺貨 |
info |
藍色 | 處理中、啟用中 |
warning |
黃色 | 待審核、庫存預警、週轉慢 |
success |
綠色 | 已完成、已核准、正常 |
destructive |
紅色 | 作廢、駁回、滯銷、異常 |
實作模式:
建議定義一個 getStatusVariant 函式將業務狀態對應到 UI 變體,保持程式碼整潔。
// 1. 定義狀態映射函式
const getStatusVariant = (status: string): StatusVariant => {
switch (status) {
case 'normal': return 'success'; // 正常 -> 綠色
case 'slow': return 'warning'; // 週轉慢 -> 黃色
case 'dead': return 'destructive'; // 滯銷 -> 紅色
case 'out_of_stock': return 'neutral';// 缺貨 -> 灰色
default: return 'neutral';
}
};
// 2. 在表格中使用
<StatusBadge variant={getStatusVariant(item.status)}>
{item.status_label}
</StatusBadge>
8. 頁面佈局規範
8.1 頁面結構
export default function ResourceIndex() {
return (
<AuthenticatedLayout
breadcrumbs={[
{ label: '分類名稱', href: '#' },
{ label: '頁面名稱', href: route('resource.index'), isPage: true },
]}
>
<Head title="頁面標題" />
<div className="container mx-auto p-6 max-w-7xl">
{/* 頁面頭部 */}
{/* 主要內容 */}
{/* 分頁元件 */}
</div>
</AuthenticatedLayout>
);
}
8.2 標準頁面頭部
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
<IconComponent className="h-6 w-6 text-[#01ab83]" />
頁面標題
</h1>
<p className="text-gray-500 mt-1">
頁面說明文字
</p>
</div>
<Can permission="resource.create">
<Link href={route('resource.create')}>
<Button className="button-filled-primary">
<Plus className="h-4 w-4 mr-2" />
新增項目
</Button>
</Link>
</Can>
</div>
9. 權限控制規範
9.1 使用 Can 元件
所有涉及權限的 UI 元素都必須使用 <Can> 元件包裹:
import { Can } from "@/Components/Permission/Can";
<Can permission="resource.create">
{/* 新增按鈕 */}
</Can>
<Can permission="resource.edit">
{/* 編輯按鈕 */}
</Can>
<Can permission="resource.delete">
{/* 刪除按鈕 */}
</Can>
9.2 權限命名規範
遵循 resource.action 格式:
resource.view:查看列表/詳情resource.create:新增resource.edit:編輯resource.delete:刪除
9.3 多權限判斷
// 滿足任一權限即可
<Can permission={['products.edit', 'products.delete']}>
<div>管理操作</div>
</Can>
// 必須滿足所有權限
import { CanAll } from "@/Components/Permission/Can";
<CanAll permissions={['products.edit', 'products.delete']}>
<button>完整管理</button>
</CanAll>
10. 通知訊息規範
10.1 使用 Toast 通知
使用 sonner 的 toast 進行通知:
import { toast } from 'sonner';
// 成功訊息
toast.success('操作成功');
// 錯誤訊息
toast.error('操作失敗');
// 資訊訊息
toast.info('提示訊息');
// 警告訊息
toast.warning('警告訊息');
10.2 常見操作的 Toast 訊息
// 新增成功
router.post(route('resource.store'), data, {
onSuccess: () => toast.success('新增成功'),
onError: () => toast.error('新增失敗,請檢查輸入內容'),
});
// 更新成功
router.put(route('resource.update', id), data, {
onSuccess: () => toast.success('更新成功'),
onError: () => toast.error('更新失敗'),
});
// 刪除成功
router.delete(route('resource.destroy', id), {
onSuccess: () => toast.success('已刪除'),
onError: () => toast.error('刪除失敗,請檢查權限'),
});
11. 表單規範
11.1 表單容器
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-6">
<form onSubmit={handleSubmit}>
{/* 表單欄位 */}
</form>
</div>
11.2 表單欄位
<div>
<label className="block text-sm font-medium text-slate-700 mb-2">
欄位名稱 <span className="text-red-500">*</span>
</label>
<input
type="text"
value={data.field}
onChange={(e) => setData("field", e.target.value)}
placeholder="請輸入..."
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-main focus:border-primary-main"
/>
{errors.field && <p className="mt-1 text-sm text-red-500">{errors.field}</p>}
</div>
11.3 下拉選單
使用 SearchableSelect 元件:
import { SearchableSelect } from "@/Components/ui/searchable-select";
<SearchableSelect
value={data.category_id}
onValueChange={(value) => setData("category_id", value)}
options={categories.map(cat => ({ label: cat.name, value: String(cat.id) }))}
placeholder="請選擇分類"
searchThreshold={10} // 超過 10 個選項才顯示搜尋框
/>
11.4 對話框 (Dialog) 滾動與佈局
當對話框內容可能超出螢幕高度時(如長表單或詳細資料),請勿使用 ScrollArea,應直接在 DialogContent 使用原生的 overflow-y-auto。
原因:ScrollArea 在 Flex 佈局計算高度時容易失效或導致雙重滾動條。以及與原生捲動行為不一致。
// ❌ 錯誤:使用 ScrollArea 或固定高度計算
<DialogContent className="max-w-3xl">
<ScrollArea className="h-[500px]">
{/* 內容 */}
</ScrollArea>
</DialogContent>
// ✅ 正確:直接使用 overflow-y-auto 與 max-h
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>...</DialogHeader>
<form className="p-6">
{/* 內容會自動滾動 */}
</form>
<DialogFooter>...</DialogFooter>
</DialogContent>
11.5 輸入框尺寸 (Input Sizes)
為確保介面整齊與統一,所有表單輸入元件標準高度應為 h-9 (36px),與標準按鈕尺寸對齊。
- Input: 預設即為
h-9(由py-1與text-sm組合而成) - Select / SearchableSelect: 必須確保 Trigger 按鈕高度為
h-9 - 禁止使用: 除非有特殊設計需求,否則避免使用
h-10(40px) 或其他非標準高度。
11.6 日期輸入框樣式 (Date Input Style)
日期輸入框應採用「左側裝飾圖示 + 右側原生操作」的配置,以保持視覺一致性並保留瀏覽器原生便利性。
樣式規格:
- 容器: 使用
relative定位。 - 圖標: 使用
Calendar圖標,放置於絕對位置absolute left-2.5 top-2.5,顏色text-gray-400,並設定pointer-events-none避免干擾點擊。 - 輸入框: 設定
pl-9(左內距) 以避開圖示,並使用原生type="date"或type="datetime-local"。
import { Calendar } from "lucide-react";
## 11.7 金額與數字輸入規範
所有涉及金額(單價、成本、總價)的輸入框,應遵循以下規範以確保操作體驗一致:
1. **HTML 屬性**:
* `type="number"`
* `min="0"` (除非業務邏輯允許負數)
* `step="any"` (設置為 `any` 可允許任意小數,且瀏覽器預設按上下鍵時會增減 **1** 並保留小數部分,例如 37.2 -> 38.2)
* **步進值 (Step)**: 金額與數量輸入框均應設定 `step="any"`,以支援小數點輸入(除非業務邏輯強制整數)。
* `placeholder="0"`
2. **樣式類別**:
* 預設靠左對齊 (不需要 `text-right`),亦可依版面需求調整。
### 9.2 對齊方式 (Alignment)
依據欄位所在的情境區分對齊方式:
- **明細列表/表格 (Details/Table)**:金額與數量欄位一律 **靠右對齊 (text-right)**。
- 包含:採購單明細、庫存盤點表、調撥單明細等 Table 內的輸入框。
- **一般表單/新增欄位 (Form/Input)**:金額與數量欄位一律 **靠左對齊 (text-left)**。
- 包含:商品資料設定、新增表單中的獨立欄位。亦可依版面需求調整。
3. **行為邏輯**:
* 輸入時允許輸入小數點。
* 鍵盤上下鍵調整時,瀏覽器會預設增減 1 (搭配 `step="any"`)。
```tsx
<Input
type="number"
min="0"
step="any"
value={price}
onChange={(e) => setPrice(parseFloat(e.target.value) || 0)}
placeholder="0"
/>
11.7 搜尋選單樣式 (SearchableSelect Style)
SearchableSelect 元件在表單或篩選列中使用時,高度必須設定為 h-9 以與輸入框對齊。
<SearchableSelect
className="h-9" // 確保高度一致
// ...other props
/>
11.8 篩選列規範 (Filter Bar Norms)
列表頁面的篩選區域(Filter Bar)應遵循以下規範以節省空間並保持層級清晰:
- 標籤文字 (Labels): 使用
text-xs(12px) 大小,顏色建議使用text-gray-500或text-grey-2。這與一般表單 (text-sm) 不同,目的是降低篩選列的視覺權重。 - 輸入元件高度: 統一使用
h-9(36px)。 - 佈局:
- 容器內距: 統一使用
p-5(20px)。 - Grid 間距: 建議使用
gap-4(16px) 或gap-6(24px),但同一專案內需統一。本專案推薦gap-4。 - 垂直間距: Label 與 Input 之間使用
space-y-1(4px)。 - 排版: 建議使用 Grid 系統 (
grid-cols-12) 進行排版。
- 容器內距: 統一使用
<div className="space-y-1">
<Label className="text-xs font-medium text-grey-2">關鍵字搜尋</Label>
<Input className="h-9" placeholder="..." />
</div>
-
操作按鈕區 (Action Bar):
- 位置: 位於篩選列最下方。
- 樣式: 統一使用
flex items-center justify-end border-t border-grey-4 pt-5 gap-3。 - 說明:
border-grey-4為標準通用邊框色,pt-5與容器 padding (p-5) 呼應,維持視覺平衡。
-
收合模式 (Collapsible Mode):
- 目的: 節省垂直空間,預設隱藏較佔空間與低頻使用的篩選器(如日期區間)。
- 實作:
- 預設狀態:若無相關篩選值,則預設為 收合 (Collapsed)。
- 切換按鈕:位於 Action Bar 左側 (
mr-auto)。 - 樣式:Ghost Button +
ChevronDown/ChevronUpIcon + 提示圓點 (Indicator)。
- 邏輯: 若載入頁面時已有被隱藏的篩選值 (e.g.
date_start),則強制 展開 (Expanded) 或顯示提示。
12. 檢查清單
在開發或審查頁面時,請確認以下項目:
✅ 按鈕
- 使用
button-filled-*或button-outlined-*類別 - 主要操作使用
button-filled-primary - 編輯操作使用
button-outlined-primary - 刪除操作使用
button-outlined-error - 按鈕尺寸正確(sm/default/lg)
- 包含適當的圖標
✅ 圖標
- 全部使用
lucide-react - 尺寸正確(h-3/h-4/h-5/h-6)
- 顏色與上下文一致
✅ 表格
- 使用
@/Components/ui/table元件 - 有
bg-white rounded-xl border容器 - 標題列有
bg-gray-50背景 - 序號欄固定寬度並置中
- 操作欄使用
flex justify-center gap-2 - 空狀態訊息置中顯示
✅ 分頁
- 使用
@/Components/shared/Pagination - 有每頁筆數選擇器(10/20/50/100)
✅ 權限
- 所有操作按鈕都用
<Can>包裹 - 權限命名符合
resource.action格式
✅ 通知
- 使用
toast提供操作反饋 - 成功/錯誤訊息明確
✅ 整體
- 頁面有標準頭部(標題 + 圖標 + 說明 + 新增按鈕)
- 容器寬度使用
max-w-7xl - 使用正確的佈局(
AuthenticatedLayout)
13. 常見錯誤與修正
❌ 錯誤:自定義按鈕樣式
// ❌ 錯誤
<Button className="bg-green-500 text-white hover:bg-green-600">
新增
</Button>
// ✅ 正確
<Button className="button-filled-primary">
<Plus className="h-4 w-4 mr-2" />
新增
</Button>
❌ 錯誤:混用圖標庫
// ❌ 錯誤
import { FaEdit } from 'react-icons/fa';
<FaEdit />
// ✅ 正確
import { Pencil } from 'lucide-react';
<Pencil className="h-4 w-4" />
❌ 錯誤:操作欄未置中
// ❌ 錯誤
<TableCell>
<Button>編輯</Button>
<Button>刪除</Button>
</TableCell>
// ✅ 正確
<TableCell className="text-center">
<div className="flex items-center justify-center gap-2">
<Button variant="outline" size="sm" className="button-outlined-primary">
<Pencil className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="button-outlined-error">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
❌ 錯誤:缺少權限控制
// ❌ 錯誤
<Button onClick={handleDelete}>刪除</Button>
// ✅ 正確
<Can permission="resource.delete">
<Button
variant="outline"
size="sm"
className="button-outlined-error"
onClick={handleDelete}
>
<Trash2 className="h-4 w-4" />
</Button>
</Can>
14. 參考範例
以下頁面展示了完整的 UI 統一性實踐:
- 使用者管理:
resources/js/Pages/Admin/User/Index.tsx - 角色管理:
resources/js/Pages/Admin/Role/Index.tsx - 產品管理:
resources/js/Pages/Product/Index.tsx - 倉庫管理:
resources/js/Pages/Warehouse/Index.tsx
總結
遵循本規範可確保:
- ✅ 視覺一致性:所有頁面看起來像同一個系統
- ✅ 維護效率:使用統一組件,修改一處即可影響全局
- ✅ 開發速度:有明確的模式可循,減少決策時間
- ✅ 使用者體驗:一致的互動模式降低學習成本
- ✅ 安全性:統一的權限控制確保資料安全
當你在開發或審查 Star ERP 客戶端後台的 UI 時,請務必參考此規範!
15. 批次匯入彈窗規範 (Batch Import Dialog)
為了確保系統中所有批次匯入功能(如:商品、庫存、客戶)的體驗一致,必須遵循以下 UI 結構與樣式。
15.1 標題結構
- 樣式:保持簡潔,僅使用文字標題,不帶額外圖示。
- 文字:統一為「匯入XXXX資料」。
<DialogHeader>
<DialogTitle>匯入商品資料</DialogTitle>
<DialogDescription>
請先下載範本,填寫完畢後上傳檔案進行批次處理。
</DialogDescription>
</DialogHeader>
15.2 分步引導區塊 (Step-by-Step Guide)
匯入流程必須分為三個清晰的步驟區塊:
步驟 1:取得匯入範本
- 容器樣式:
bg-gray-50 rounded-lg border border-gray-100 p-4 space-y-2 - 標題圖示:
<FileSpreadsheet className="w-4 h-4 text-green-600" /> - 下載按鈕:
variant="outline" size="sm" className="w-full sm:w-auto button-outlined-primary",並明確標註.xlsx。
步驟 2:設定資訊 (選甜)
- 容器樣式:
space-y-2 - 標題圖示:
<Info className="w-4 h-4 text-primary-main" /> - 欄位樣式:使用標準
Input,標籤文字使用text-sm text-gray-700。 - 預設值:若有備註欄位,應提供合適的預設值(例如:「Excel 匯入」)。
步驟 3:上傳填寫後的檔案
- 容器樣式:
space-y-2 - 標題圖示:
<FileUp className="w-4 h-4 text-blue-600" /> - Input 樣式:
type="file",並開啟cursor-pointer。
15.3 規則說明面板 (Accordion Rules)
詳細的填寫說明必須收納於 Accordion 中,避免干擾主流程:
- 樣式:標準灰色邊框,不使用特殊背景色 (如琥珀色)。
- 容器:
className="w-full border rounded-lg px-2" - 觸發文字:
text-sm text-gray-500
<Accordion type="single" collapsible className="w-full border rounded-lg px-2">
<AccordionItem value="rules" className="border-b-0">
<AccordionTrigger className="text-sm text-gray-500 hover:no-underline py-3">
<div className="flex items-center gap-2">
<Info className="h-4 w-4" />
匯入規則與提示
</div>
</AccordionTrigger>
<AccordionContent>
<div className="text-sm text-gray-600 space-y-2 pb-2 pl-6">
<ul className="list-disc space-y-1">
<li>使用加粗文字標註關鍵欄位:<span className="font-medium text-gray-700">關鍵字</span></li>
<li>說明文字簡潔明瞭。</li>
</ul>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
15.4 底部操作 (Footer)
- 取消按鈕:
variant="outline",且為button-outlined-primary。 - 提交按鈕:
button-filled-primary,且在處理中時顯示Loader2。
16. 詳情頁面項目清單規範 (Detail Page Item List Standards)
為了確保詳情頁面(如:採購單詳情、進貨單詳情、銷售匯入詳情)的資訊層級清晰且視覺統一,所有項目清單必須遵循以下規範。
16.1 容器結構 (Container Structure)
項目清單應封裝在一個帶有內距的卡片容器中,而不是讓表格直接緊貼外層卡片邊緣。
- 外層卡片:
bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden - 標題區塊:
p-6 border-b border-gray-100 bg-gray-50/30 - 內容內距:標題下方的內容區塊應加上
p-6。 - 表格包裹層:表格應再包裹一層
border rounded-lg overflow-hidden,以確保表格內部的邊角與隔線視覺完整。
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
{/* 標題 */}
<div className="p-6 border-b border-gray-100 bg-gray-50/30">
<h2 className="text-lg font-bold text-gray-900">項目清單標題</h2>
</div>
{/* 內容區塊 */}
<div className="p-6">
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-gray-50 hover:bg-gray-50">
{/* 標頭欄位 */}
</TableRow>
</TableHeader>
<TableBody>
{/* 表格內容 */}
</TableBody>
</Table>
</div>
{/* 若有分頁,直接放在 p-6 容器內,並加 mt-6 分隔 */}
<div className="mt-6">
<Pagination ... />
</div>
</div>
</div>
16.2 表格樣式細節 (Table Styling)
- 標頭背景:
TableHeader的第一個TableRow應使用bg-gray-50 hover:bg-gray-50強化視覺區隔。 - 文字顏色:主體文字使用
text-gray-900(標題/重要數據)或text-gray-500(輔助/序號)。 - 數據對齊:
- 數量/序號:文字置中 (
text-center) 或依據數據類型對齊。 - 金額:金額欄位必須使用
text-right並視情況加粗 (font-bold) 或加上text-primary-main顏色。
- 數量/序號:文字置中 (
- 表格隔線:確保表格具有清晰但不過於突出的水平隔線,提升長列表的可讀性。