feat(Inventory): 實作批號溯源完整功能與 UI 呈現,包含文字敘述卡片與更完整的關聯屬性

This commit is contained in:
2026-02-26 10:39:24 +08:00
parent 63e4f88a14
commit f960aaaeb2
16 changed files with 1085 additions and 694 deletions

View File

@@ -7,506 +7,93 @@ name: 客戶端後台 UI 統一規範
description: 確保 Star ERP 客戶端(租戶端)後台所有頁面的 UI 元件保持統一的樣式與行為
---
## 概述
## 適用範圍
技能提供 Star ERP 系統**客戶端(租戶端後台**的 UI 統一性規範,確保所有頁面使用一致的元件、樣式類別、圖標和佈局模式
規範適用於租戶端後台(使用 `AuthenticatedLayout` 的頁面),**不適用於**中央管理後台(`LandlordLayout`
> **適用範圍**:本規範適用於租戶端後台(使用 `AuthenticatedLayout` 的頁面),**不適用於**中央管理後台(`LandlordLayout`)。
## 核心禁止事項
## 核心原則
1. **使用統一的 UI 組件庫**:優先使用 `@/Components/ui/` 中的 47 個元件
2. **遵循既定的樣式類別**:使用 `app.css` 中定義的自定義按鈕類別
3. **統一的圖標系統**:全面使用 `lucide-react` 圖標
4. **一致的佈局模式**:表格、分頁、操作按鈕等保持相同結構
5. **權限控制**:所有操作按鈕必須使用 `<Can>` 元件包裹
- ❌ **禁止 Hardcode 色碼**(如 `text-[#01ab83]`),必須使用 `*-primary-main` 等 Tailwind Class 或 CSS 變數
- ❌ **禁止使用非 `lucide-react` 的圖標庫**(如 FontAwesome、Material Icons
- ❌ **禁止操作按鈕不包裹 `<Can>` 權限元件**
---
## 1. 專案結構
## 1. 色彩系統
### 1.1 關鍵目錄
### 主題色(動態租戶品牌色,由 `AuthenticatedLayout` 自動注入)
```
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/ # 頁面元件
```
| Tailwind Class | 用途 |
|---|---|
| `*-primary-main` | 主色:按鈕、連結、強調 |
| `*-primary-dark` | Hover 狀態 |
| `*-primary-light` | 次要強調 |
| `*-primary-lightest` | 背景底色、Active 狀態 |
### 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
```
直接參考 `resources/css/app.css` 中定義的 `--grey-0` ~ `--grey-5``--other-success/error/warning/info` 變數。
---
## 2. 色彩系統
## 2. 按鈕規範
### 2.1 主題色 (Primary) - **動態租戶品牌色**
樣式定義於 `resources/css/app.css`,按鈕必須使用以下類別:
> **注意**主題色會根據租戶設定Branding動態改變**嚴禁**在程式碼中 Hardcode 色碼(如 `#01ab83`)。
> 請務必使用 Tailwind Utility Class 或 CSS 變數。
| 類型 | 類別 | 用途 |
|---|---|---|
| Filled | `button-filled-primary` | 主要操作(新增、儲存) |
| Filled | `button-filled-success/info/warning/error` | 各狀態操作 |
| Outlined | `button-outlined-primary` | 次要操作(編輯、檢視) |
| Outlined | `button-outlined-error` | 刪除按鈕 |
| Text | `button-text-primary` | 文字連結式按鈕 |
| Tailwind Class | CSS Variable | 說明 |
|----------------|--------------|------|
| `*-primary-main` | `--primary-main` | **主色**:與租戶設定一致(預設綠色),用於主要按鈕、連結、強調文字 |
| `*-primary-dark` | `--primary-dark` | **深色**:系統自動計算,用於 Hover 狀態 |
| `*-primary-light` | `--primary-light` | **淺色**:系統自動計算,用於次要強調 |
| `*-primary-lightest` | `--primary-lightest` | **最淺色**系統自動計算用於背景底色、Active 狀態 |
**尺寸**:表格操作列用 `size="sm"`,一般操作用 `size="default"`,主要 CTA 用 `size="lg"`
**運作機制**
`AuthenticatedLayout` 會根據後端回傳的 `branding` 資料,自動注入 CSS 變數覆寫預設值。
```tsx
// ✅ 正確:使用 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)
```css
--grey-0: #1a1a1a; /* 深黑 - 標題文字 */
--grey-1: #4a4a4a; /* 深灰 - 主要內文 */
--grey-2: #6b6b6b; /* 中灰 - 次要內文、Placeholder */
--grey-3: #9e9e9e; /* 淺灰 - 禁用文字、輔助說明 */
--grey-4: #e0e0e0; /* 極淺灰 - 邊框、分隔線 */
--grey-5: #fff; /* 白色 - 背景、按鈕文字 */
```
### 2.3 狀態色 (State Colors)
```css
--other-success: #01ab83; /* 成功 - 同主題色 */
--other-error: #dc2626; /* 錯誤 - 刪除、警示 */
--other-warning: #f59e0b; /* 警告 - 提醒、注意 */
--other-info: #3b82f6; /* 資訊 - 說明、提示 */
```
**返回按鈕**:放置於標題上方,使用 `variant="outline"` + `className="gap-2 button-outlined-primary"`,搭配 `<ArrowLeft />` 圖標。
---
## 3. 按鈕規範
## 3. 圖標規範
### 3.1 按鈕樣式類別
統一使用 `lucide-react`
專案在 `resources/css/app.css` 中定義了統一的按鈕樣式,**必須**使用這些類別:
| 尺寸 | 用途 |
|---|---|
| `h-4 w-4` | 按鈕內、表格操作 |
| `h-5 w-5` | 側邊欄選單 |
| `h-6 w-6` | 頁面標題 |
#### Filled 按鈕(實心按鈕)— 用於主要操作
```tsx
// ✅ 主要操作按鈕(綠色主題色)- 新增、儲存、確認
<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 按鈕(邊框按鈕)— 用於次要操作
```tsx
// ✅ 編輯按鈕(表格操作列)
<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 按鈕(文字按鈕)
```tsx
<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 常見操作按鈕模式
#### 頁面頂部新增按鈕
```tsx
<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>
```
#### 表格操作列檢視按鈕
```tsx
<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>
```
#### 表格操作列編輯按鈕
```tsx
<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>
```
#### 表格操作列刪除按鈕(帶確認對話框)
```tsx
<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" />`
- **文字**:清楚說明返回目的地,例如「返回倉庫管理」、「返回列表」
```tsx
<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>
```
常用映射:`Plus`(新增)、`Pencil`(編輯)、`Trash2`(刪除)、`Eye`(檢視)、`Search`(搜尋)、`ArrowLeft`(返回)。
其餘請參考 `AuthenticatedLayout.tsx` 中的 `allMenuItems` 定義。
---
## 3.5 頁面佈局規範(新增/編輯頁面)
## 4. 頁面佈局規範
### 標準結構
所有頁面遵循以下結構,參考範例:`Pages/Product/Create.tsx``Pages/PurchaseOrder/Create.tsx`
新增/編輯頁面(如:商品新增、採購單建立)應遵循以下標準結構
```tsx
<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>
```
### 關鍵規範
1. **外層容器**:使用 `className="container mx-auto p-6 max-w-7xl"` 確保寬度與間距一致
2. **Header 包裹**:使用 `<div className="mb-6">` 包裹返回按鈕與標題區塊
3. **返回按鈕**:加上 `mb-4` 與標題區塊分隔
4. **標題區塊**:使用 `<div className="mb-4">` 包裹 h1 和 p 標籤
5. **標題樣式**`text-2xl font-bold text-grey-0 flex items-center gap-2`
6. **說明文字**`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 圖標使用範例
```tsx
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>
```
**關鍵規則**
- **外層容器**`className="container mx-auto p-6 max-w-7xl"`
- **標題樣式**`text-2xl font-bold text-grey-0 flex items-center gap-2`
- **說明文字**`text-gray-500 mt-1`
- **麵包屑**:使用 `BreadcrumbItemType`(屬性為 `label`, `href`, `isPage`),不需要包含「首頁」
- **Input 元件**:不額外設定 focus 樣式,直接使用 `@/Components/ui/input` 的內建樣式
- **日期顯示**:使用 `resources/js/lib/date.ts``formatDate` 工具
---
## 5. 表格規範
### 5.1 表格容器
**容器**`bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden`
**標題列**`bg-gray-50`,序號欄 `w-[50px] text-center`,操作欄置中
**空狀態**`text-center py-8 text-gray-500`,顯示「無符合條件的資料」
**操作欄**`flex items-center justify-center gap-2`
```tsx
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<Table>
{/* 表格內容 */}
</Table>
</div>
```
### 排序(三態切換)
### 5.2 表格標題列
```tsx
<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 表格主體
```tsx
<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 欄位排序規範
當表格需要支援排序時,請遵循以下模式:
1. **圖標邏輯**
* 未排序:`ArrowUpDown` (class: `text-muted-foreground`)
* 升冪 (asc)`ArrowUp` (class: `text-primary`)
* 降冪 (desc)`ArrowDown` (class: `text-primary`)
2. **結構**:在 `TableHead` 內使用 `button` 元素。
3. **後端配合**:後端 Controller **必須** 處理 `sort_by``sort_order` 參數。
```tsx
// 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().curr
- 未排序:`ArrowUpDown``text-muted-foreground`
- 升冪:`ArrowUp``text-primary`
- 降冪:`ArrowDown``text-primary`
- 後端必須處理 `sort_by``sort_order` 參數
- 參考實作:`Pages/Product/Index.tsx``handleSort`