2026-01-14 11:31:36 +08:00
|
|
|
|
---
|
2026-01-16 10:33:39 +08:00
|
|
|
|
name: 客戶端後台 UI 統一規範
|
|
|
|
|
|
description: 確保 Star ERP 客戶端(租戶端)後台所有頁面的 UI 元件保持統一的樣式與行為
|
2026-01-14 11:31:36 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
# 客戶端後台 UI 統一規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
本技能提供 Star ERP 系統**客戶端(租戶端)後台**的 UI 統一性規範,確保所有頁面使用一致的元件、樣式類別、圖標和佈局模式。
|
|
|
|
|
|
|
|
|
|
|
|
> **適用範圍**:本規範適用於租戶端後台(使用 `AuthenticatedLayout` 的頁面),**不適用於**中央管理後台(`LandlordLayout`)。
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
## 核心原則
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
1. **使用統一的 UI 組件庫**:優先使用 `@/Components/ui/` 中的 47 個元件
|
2026-01-14 11:31:36 +08:00
|
|
|
|
2. **遵循既定的樣式類別**:使用 `app.css` 中定義的自定義按鈕類別
|
|
|
|
|
|
3. **統一的圖標系統**:全面使用 `lucide-react` 圖標
|
|
|
|
|
|
4. **一致的佈局模式**:表格、分頁、操作按鈕等保持相同結構
|
2026-01-16 10:33:39 +08:00
|
|
|
|
5. **權限控制**:所有操作按鈕必須使用 `<Can>` 元件包裹
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 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. 色彩系統
|
|
|
|
|
|
|
2026-01-19 09:30:02 +08:00
|
|
|
|
### 2.1 主題色 (Primary) - **動態租戶品牌色**
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-19 09:30:02 +08:00
|
|
|
|
> **注意**:主題色會根據租戶設定(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 變數覆寫預設值。
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
// ✅ 正確:使用 Tailwind Class
|
|
|
|
|
|
<div className="text-primary-main">...</div>
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正確:使用 CSS 變數 (自定義樣式時)
|
|
|
|
|
|
<div style={{ borderColor: 'var(--primary-main)' }}>...</div>
|
|
|
|
|
|
|
|
|
|
|
|
// ❌ 錯誤:寫死色碼 (會導致租戶無法換色)
|
|
|
|
|
|
<div className="text-[#01ab83]">...</div>
|
2026-01-16 10:33:39 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 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; /* 資訊 - 說明、提示 */
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 3. 按鈕規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 3.1 按鈕樣式類別
|
|
|
|
|
|
|
|
|
|
|
|
專案在 `resources/css/app.css` 中定義了統一的按鈕樣式,**必須**使用這些類別:
|
|
|
|
|
|
|
|
|
|
|
|
#### Filled 按鈕(實心按鈕)— 用於主要操作
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 主要操作按鈕(綠色主題色)- 新增、儲存、確認
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-primary">
|
|
|
|
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
|
|
|
|
新增項目
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 成功操作
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-success">確認</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 資訊操作
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-info">查看詳情</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 警告操作
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-warning">警告</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 錯誤/刪除操作(AlertDialog 內確認按鈕)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-error">刪除</Button>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
#### Outlined 按鈕(邊框按鈕)— 用於次要操作
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 編輯按鈕(表格操作列)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button variant="outline" size="sm" className="button-outlined-primary">
|
|
|
|
|
|
<Pencil className="h-4 w-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 刪除按鈕(表格操作列)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button variant="outline" size="sm" className="button-outlined-error">
|
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### Text 按鈕(文字按鈕)
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<Button className="button-text-primary">查看更多</Button>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 3.2 按鈕大小
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
| Size | 高度 | 使用情境 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| `size="sm"` | h-8 | 表格操作列、緊湊佈局 |
|
|
|
|
|
|
| `size="default"` | h-9 | 一般操作、表單提交 |
|
|
|
|
|
|
| `size="lg"` | h-10 | 主要 CTA、頁面主操作 |
|
|
|
|
|
|
| `size="icon"` | 9×9 | 純圖標按鈕 |
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 3.3 常見操作按鈕模式
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
#### 頁面頂部新增按鈕
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
#### 表格操作列編輯按鈕
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
#### 表格操作列刪除按鈕(帶確認對話框)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 4. 圖標規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 4.1 統一使用 lucide-react
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
**統一使用 `lucide-react`**,禁止使用其他圖標庫(如 FontAwesome、Material Icons、react-icons 等)。
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 4.2 圖標尺寸標準
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
| 尺寸 | 類別 | 使用情境 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| 小型 | `h-3 w-3` | Badge 內、小文字旁 |
|
|
|
|
|
|
| 標準 | `h-4 w-4` | 按鈕內、表格操作 |
|
|
|
|
|
|
| 標題 | `h-5 w-5` | 側邊欄選單 |
|
|
|
|
|
|
| 大型 | `h-6 w-6` | 頁面標題 |
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 4.3 常用操作圖標映射
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
| 操作 | 圖標組件 | 使用情境 |
|
|
|
|
|
|
|------|----------|----------|
|
|
|
|
|
|
| 新增 | `<Plus />` | 新增按鈕 |
|
|
|
|
|
|
| 編輯 | `<Pencil />` | 編輯按鈕 |
|
|
|
|
|
|
| 刪除 | `<Trash2 />` | 刪除按鈕 |
|
|
|
|
|
|
| 查看 | `<Eye />` | 查看詳情 |
|
|
|
|
|
|
| 搜尋 | `<Search />` | 搜尋欄位 |
|
|
|
|
|
|
| 篩選 | `<Filter />` | 篩選功能 |
|
|
|
|
|
|
| 下載 | `<Download />` | 下載/匯出 |
|
|
|
|
|
|
| 上傳 | `<Upload />` | 上傳/匯入 |
|
|
|
|
|
|
| 設定 | `<Settings />` | 設定功能 |
|
|
|
|
|
|
| 複製 | `<Copy />` | 複製內容 |
|
|
|
|
|
|
| 郵件 | `<Mail />` | Email 顯示 |
|
|
|
|
|
|
| 使用者 | `<Users />`, `<User />` | 使用者管理 |
|
|
|
|
|
|
| 權限 | `<Shield />` | 角色/權限 |
|
|
|
|
|
|
| 排序 | `<ArrowUpDown />`, `<ArrowUp />`, `<ArrowDown />` | 表格排序 |
|
2026-01-16 10:33:39 +08:00
|
|
|
|
| 儀表板 | `<LayoutDashboard />` | 首頁/總覽 |
|
|
|
|
|
|
| 商品 | `<Package />` | 商品管理 |
|
|
|
|
|
|
| 倉庫 | `<Warehouse />` | 倉庫管理 |
|
|
|
|
|
|
| 廠商 | `<Truck />`, `<Contact2 />` | 廠商管理 |
|
|
|
|
|
|
| 採購 | `<ShoppingCart />` | 採購管理 |
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 4.4 圖標使用範例
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
import { Plus, Pencil, Trash2, Users } from 'lucide-react';
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 頁面標題
|
|
|
|
|
|
<h1 className="text-2xl font-bold text-grey-0 flex items-center gap-2">
|
|
|
|
|
|
<Users className="h-6 w-6 text-[#01ab83]" />
|
|
|
|
|
|
使用者管理
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// 按鈕內圖標(圖標在左,帶文字)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 5. 表格規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 5.1 表格容器
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
|
|
|
|
|
|
<Table>
|
|
|
|
|
|
{/* 表格內容 */}
|
|
|
|
|
|
</Table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 5.2 表格標題列
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<TableHeader className="bg-gray-50">
|
|
|
|
|
|
<TableRow>
|
|
|
|
|
|
<TableHead className="w-[50px] text-center">#</TableHead>
|
2026-01-16 10:33:39 +08:00
|
|
|
|
<TableHead>名稱</TableHead>
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<TableHead className="text-center">操作</TableHead>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
</TableHeader>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**關鍵要點**:
|
|
|
|
|
|
- 使用 `bg-gray-50` 背景色
|
|
|
|
|
|
- 序號欄位固定寬度 `w-[50px]` 並置中
|
|
|
|
|
|
- 操作欄位置中顯示
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 5.3 表格主體
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<TableBody>
|
|
|
|
|
|
{items.length === 0 ? (
|
|
|
|
|
|
<TableRow>
|
2026-01-16 10:33:39 +08:00
|
|
|
|
<TableCell colSpan={5} className="text-center py-8 text-gray-500">
|
2026-01-14 11:31:36 +08:00
|
|
|
|
無符合條件的資料
|
|
|
|
|
|
</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">
|
2026-01-16 10:33:39 +08:00
|
|
|
|
<div className="flex items-center justify-center gap-2">
|
2026-01-14 11:31:36 +08:00
|
|
|
|
{/* 操作按鈕 */}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableBody>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**關鍵要點**:
|
|
|
|
|
|
- 空狀態訊息使用置中、灰色文字
|
|
|
|
|
|
- 序號欄使用 `text-gray-500 font-medium text-center`
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- 操作欄使用 `flex items-center justify-center gap-2` 排列按鈕
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-19 09:30:02 +08:00
|
|
|
|
### 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().current()!),
|
|
|
|
|
|
{ ...filters, sort_by: newSortBy, sort_order: newSortOrder },
|
|
|
|
|
|
{ preserveState: true, replace: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-14 11:31:36 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 6. 分頁規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 6.1 統一分頁元件
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
使用 `@/Components/shared/Pagination` 元件:
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
import Pagination from "@/Components/shared/Pagination";
|
2026-01-16 10:33:39 +08:00
|
|
|
|
import { SearchableSelect } from "@/Components/ui/searchable-select";
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 在表格下方
|
|
|
|
|
|
<div className="mt-4 flex flex-col sm:flex-row items-center justify-between 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-[80px] h-8"
|
|
|
|
|
|
showSearch={false}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>筆</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Pagination links={data.links} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 6.2 每頁筆數狀態管理
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
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 }
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 7. Badge 與狀態顯示
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 7.1 基本 Badge
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
import { Badge } from "@/Components/ui/badge";
|
|
|
|
|
|
|
|
|
|
|
|
// Outline 樣式(最常用)
|
2026-01-16 10:33:39 +08:00
|
|
|
|
<Badge variant="outline">{item.category?.name || '-'}</Badge>
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 預設樣式(主題色背景)
|
|
|
|
|
|
<Badge variant="default">啟用中</Badge>
|
|
|
|
|
|
|
|
|
|
|
|
// 錯誤樣式
|
|
|
|
|
|
<Badge variant="destructive">停用</Badge>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 7.2 角色顯示(特殊樣式)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
{user.roles.map(role => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={role.id}
|
|
|
|
|
|
className={cn(
|
2026-01-14 14:57:05 +08:00
|
|
|
|
"inline-flex items-center px-2.5 py-1 rounded-md border",
|
2026-01-14 11:31:36 +08:00
|
|
|
|
role.name === 'super-admin'
|
|
|
|
|
|
? "bg-purple-50 border-purple-200"
|
|
|
|
|
|
: "bg-gray-50 border-gray-200"
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
2026-01-14 14:57:05 +08:00
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
|
|
|
{role.name === 'super-admin' && <Shield className="h-3.5 w-3.5 text-purple-600" />}
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<span className={cn(
|
|
|
|
|
|
"text-sm font-medium",
|
|
|
|
|
|
role.name === 'super-admin' ? "text-purple-700" : "text-gray-900"
|
|
|
|
|
|
)}>
|
|
|
|
|
|
{role.display_name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 8. 頁面佈局規範
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 頁面結構
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 8.2 標準頁面頭部
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
<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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 9. 權限控制規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 9.1 使用 Can 元件
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
**所有**涉及權限的 UI 元素都必須使用 `<Can>` 元件包裹:
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
import { Can } from "@/Components/Permission/Can";
|
|
|
|
|
|
|
|
|
|
|
|
<Can permission="resource.create">
|
|
|
|
|
|
{/* 新增按鈕 */}
|
|
|
|
|
|
</Can>
|
|
|
|
|
|
|
|
|
|
|
|
<Can permission="resource.edit">
|
|
|
|
|
|
{/* 編輯按鈕 */}
|
|
|
|
|
|
</Can>
|
|
|
|
|
|
|
|
|
|
|
|
<Can permission="resource.delete">
|
|
|
|
|
|
{/* 刪除按鈕 */}
|
|
|
|
|
|
</Can>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 9.2 權限命名規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
遵循 `resource.action` 格式:
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- `resource.view`:查看列表/詳情
|
2026-01-14 11:31:36 +08:00
|
|
|
|
- `resource.create`:新增
|
|
|
|
|
|
- `resource.edit`:編輯
|
|
|
|
|
|
- `resource.delete`:刪除
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 9.3 多權限判斷
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
// 滿足任一權限即可
|
|
|
|
|
|
<Can permission={['products.edit', 'products.delete']}>
|
|
|
|
|
|
<div>管理操作</div>
|
|
|
|
|
|
</Can>
|
|
|
|
|
|
|
|
|
|
|
|
// 必須滿足所有權限
|
|
|
|
|
|
import { CanAll } from "@/Components/Permission/Can";
|
|
|
|
|
|
|
|
|
|
|
|
<CanAll permissions={['products.edit', 'products.delete']}>
|
|
|
|
|
|
<button>完整管理</button>
|
|
|
|
|
|
</CanAll>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-14 11:31:36 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 10. 通知訊息規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 10.1 使用 Toast 通知
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
使用 `sonner` 的 `toast` 進行通知:
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
|
|
|
|
|
|
|
// 成功訊息
|
|
|
|
|
|
toast.success('操作成功');
|
|
|
|
|
|
|
|
|
|
|
|
// 錯誤訊息
|
|
|
|
|
|
toast.error('操作失敗');
|
|
|
|
|
|
|
|
|
|
|
|
// 資訊訊息
|
|
|
|
|
|
toast.info('提示訊息');
|
|
|
|
|
|
|
|
|
|
|
|
// 警告訊息
|
|
|
|
|
|
toast.warning('警告訊息');
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 10.2 常見操作的 Toast 訊息
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
// 新增成功
|
|
|
|
|
|
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('刪除失敗,請檢查權限'),
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 11. 表單規範
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 11.1 表單容器
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
```tsx
|
|
|
|
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6 space-y-6">
|
|
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
|
|
|
|
{/* 表單欄位 */}
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
```
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 11.2 表單欄位
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
```tsx
|
|
|
|
|
|
<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>
|
2026-01-14 11:31:36 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
### 11.3 下拉選單
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
使用 `SearchableSelect` 元件:
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
|
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 個選項才顯示搜尋框
|
|
|
|
|
|
/>
|
2026-01-14 11:31:36 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 12. 檢查清單
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
在開發或審查頁面時,請確認以下項目:
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 按鈕
|
|
|
|
|
|
- [ ] 使用 `button-filled-*` 或 `button-outlined-*` 類別
|
|
|
|
|
|
- [ ] 主要操作使用 `button-filled-primary`
|
|
|
|
|
|
- [ ] 編輯操作使用 `button-outlined-primary`
|
|
|
|
|
|
- [ ] 刪除操作使用 `button-outlined-error`
|
|
|
|
|
|
- [ ] 按鈕尺寸正確(sm/default/lg)
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- [ ] 包含適當的圖標
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
### ✅ 圖標
|
|
|
|
|
|
- [ ] 全部使用 `lucide-react`
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- [ ] 尺寸正確(h-3/h-4/h-5/h-6)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
- [ ] 顏色與上下文一致
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 表格
|
|
|
|
|
|
- [ ] 使用 `@/Components/ui/table` 元件
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- [ ] 有 `bg-white rounded-xl border` 容器
|
2026-01-14 11:31:36 +08:00
|
|
|
|
- [ ] 標題列有 `bg-gray-50` 背景
|
|
|
|
|
|
- [ ] 序號欄固定寬度並置中
|
|
|
|
|
|
- [ ] 操作欄使用 `flex justify-center gap-2`
|
|
|
|
|
|
- [ ] 空狀態訊息置中顯示
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 分頁
|
|
|
|
|
|
- [ ] 使用 `@/Components/shared/Pagination`
|
|
|
|
|
|
- [ ] 有每頁筆數選擇器(10/20/50/100)
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 權限
|
|
|
|
|
|
- [ ] 所有操作按鈕都用 `<Can>` 包裹
|
|
|
|
|
|
- [ ] 權限命名符合 `resource.action` 格式
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 通知
|
|
|
|
|
|
- [ ] 使用 `toast` 提供操作反饋
|
|
|
|
|
|
- [ ] 成功/錯誤訊息明確
|
|
|
|
|
|
|
|
|
|
|
|
### ✅ 整體
|
|
|
|
|
|
- [ ] 頁面有標準頭部(標題 + 圖標 + 說明 + 新增按鈕)
|
|
|
|
|
|
- [ ] 容器寬度使用 `max-w-7xl`
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- [ ] 使用正確的佈局(`AuthenticatedLayout`)
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 13. 常見錯誤與修正
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
### ❌ 錯誤:自定義按鈕樣式
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ❌ 錯誤
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="bg-green-500 text-white hover:bg-green-600">
|
|
|
|
|
|
新增
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 正確
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button className="button-filled-primary">
|
|
|
|
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
|
|
|
|
新增
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ❌ 錯誤:混用圖標庫
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ❌ 錯誤
|
|
|
|
|
|
import { FaEdit } from 'react-icons/fa';
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<FaEdit />
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 正確
|
|
|
|
|
|
import { Pencil } from 'lucide-react';
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Pencil className="h-4 w-4" />
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ❌ 錯誤:操作欄未置中
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ❌ 錯誤
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<TableCell>
|
|
|
|
|
|
<Button>編輯</Button>
|
|
|
|
|
|
<Button>刪除</Button>
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 正確
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<TableCell className="text-center">
|
2026-01-16 10:33:39 +08:00
|
|
|
|
<div className="flex items-center justify-center gap-2">
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ❌ 錯誤:缺少權限控制
|
|
|
|
|
|
|
|
|
|
|
|
```tsx
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ❌ 錯誤
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Button onClick={handleDelete}>刪除</Button>
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
// ✅ 正確
|
2026-01-14 11:31:36 +08:00
|
|
|
|
<Can permission="resource.delete">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
className="button-outlined-error"
|
|
|
|
|
|
onClick={handleDelete}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Can>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
## 14. 參考範例
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
以下頁面展示了完整的 UI 統一性實踐:
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
- **使用者管理**:`resources/js/Pages/Admin/User/Index.tsx`
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- **角色管理**:`resources/js/Pages/Admin/Role/Index.tsx`
|
2026-01-14 11:31:36 +08:00
|
|
|
|
- **產品管理**:`resources/js/Pages/Product/Index.tsx`
|
2026-01-16 10:33:39 +08:00
|
|
|
|
- **倉庫管理**:`resources/js/Pages/Warehouse/Index.tsx`
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 總結
|
|
|
|
|
|
|
|
|
|
|
|
遵循本規範可確保:
|
|
|
|
|
|
|
|
|
|
|
|
1. ✅ **視覺一致性**:所有頁面看起來像同一個系統
|
|
|
|
|
|
2. ✅ **維護效率**:使用統一組件,修改一處即可影響全局
|
|
|
|
|
|
3. ✅ **開發速度**:有明確的模式可循,減少決策時間
|
|
|
|
|
|
4. ✅ **使用者體驗**:一致的互動模式降低學習成本
|
2026-01-16 10:33:39 +08:00
|
|
|
|
5. ✅ **安全性**:統一的權限控制確保資料安全
|
2026-01-14 11:31:36 +08:00
|
|
|
|
|
2026-01-16 10:33:39 +08:00
|
|
|
|
當你在開發或審查 Star ERP 客戶端後台的 UI 時,請務必參考此規範!
|