Files
star-erp/.agent/skills/ui-consistency/SKILL.md
sky121113 f7238c2860
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 51s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped
fix: 統一 UI 按鈕樣式並新增 button-outlined-error hover 效果
- 修正 5 處硬編碼顏色樣式改用預定義按鈕類別
- 新增 button-outlined-error 的 hover 狀態(bg-red-50)
- 修正倉庫模組刪除按鈕樣式統一性
- 角色管理權限 Badge 改用標準組件
- 新增 UI 統一性規範 skill
- 修復 1 處 lint 警告(移除未使用參數)

變更檔案:
- resources/css/app.css: 新增 button-outlined-error hover 樣式
- resources/js/Components/Warehouse/WarehouseDialog.tsx
- resources/js/Pages/Admin/Role/Index.tsx
- resources/js/Pages/Warehouse/EditInventory.tsx
- resources/js/Pages/Warehouse/Inventory.tsx
- resources/js/Pages/Warehouse/SafetyStockSettings.tsx
- .agent/skills/ui-consistency/SKILL.md (新增)
2026-01-14 11:31:36 +08:00

17 KiB
Raw Blame History

name, description
name description
UI 統一性規範 確保 koori-erp ERP 系統後台所有頁面的 UI 元件保持統一的樣式與行為

UI 統一性規範

概述

本 skill 提供 koori-erp ERP 系統的 UI 統一性規範,確保所有頁面使用一致的元件、樣式類別、圖標和佈局模式。

核心原則

  1. 使用統一的 UI 組件庫:優先使用 @/Components/ui/ 中的元件
  2. 遵循既定的樣式類別:使用 app.css 中定義的自定義按鈕類別
  3. 統一的圖標系統:全面使用 lucide-react 圖標
  4. 一致的佈局模式:表格、分頁、操作按鈕等保持相同結構

1. 按鈕規範

1.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>

// 錯誤/刪除操作
<Button className="button-filled-error">刪除</Button>

Outlined 按鈕(邊框按鈕)

// 次要操作(主題色邊框)
<Button variant="outline" size="sm" className="button-outlined-primary">
  <Pencil className="h-4 w-4" />
</Button>

// 成功樣式邊框
<Button className="button-outlined-success">成功</Button>

// 資訊樣式邊框
<Button className="button-outlined-info">資訊</Button>

// 警告樣式邊框
<Button className="button-outlined-warning">警告</Button>

// 錯誤/刪除樣式邊框
<Button variant="outline" size="sm" className="button-outlined-error">
  <Trash2 className="h-4 w-4" />
</Button>

Text 按鈕(文字按鈕)

// 文字按鈕
<Button className="button-text-primary">查看更多</Button>

1.2 按鈕大小

使用 shadcn/ui Button 組件的標準尺寸:

  • size="sm"小型按鈕h-8用於表格操作列
  • size="default"預設按鈕h-9用於一般操作
  • size="lg"大型按鈕h-10用於主要 CTA
  • size="icon"圖標按鈕size-9僅含圖標的正方形按鈕

1.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.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>

2. 圖標規範

2.1 圖標庫

統一使用 lucide-react,不使用其他圖標庫(如 FontAwesome、Material Icons 等)。

2.2 圖標尺寸標準

  • 小型圖標h-3 w-3(用於 Badge、小文字旁
  • 標準圖標h-4 w-4(用於按鈕、表格操作)
  • 標題圖標h-6 w-6(用於頁面標題)

2.3 常用操作圖標映射

操作 圖標組件 使用情境
新增 <Plus /> 新增按鈕
編輯 <Pencil /> 編輯按鈕
刪除 <Trash2 /> 刪除按鈕
查看 <Eye /> 查看詳情
搜尋 <Search /> 搜尋欄位
篩選 <Filter /> 篩選功能
下載 <Download /> 下載/匯出
上傳 <Upload /> 上傳/匯入
設定 <Settings /> 設定功能
複製 <Copy /> 複製內容
郵件 <Mail /> Email 顯示
使用者 <Users />, <User /> 使用者管理
權限 <Shield /> 角色/權限
排序 <ArrowUpDown />, <ArrowUp />, <ArrowDown /> 表格排序

2.4 圖標使用範例

import { Plus, Pencil, Trash2, Eye, Search } 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>

3. 表格規範

3.1 表格容器

使用統一的表格包裝樣式:

<div className="bg-white rounded-lg shadow-sm border">
  <Table>
    {/* 表格內容 */}
  </Table>
</div>

或使用更精緻的樣式(用於管理頁面):

<div className="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
  <Table>
    {/* 表格內容 */}
  </Table>
</div>

3.2 表格標題列

<TableHeader className="bg-gray-50">
  <TableRow>
    <TableHead className="w-[50px] text-center">#</TableHead>
    <TableHead>
      <button 
        onClick={() => onSort("name")} 
        className="flex items-center hover:text-gray-900 font-semibold"
      >
        名稱 <SortIcon field="name" />
      </button>
    </TableHead>
    <TableHead className="text-center">操作</TableHead>
  </TableRow>
</TableHeader>

關鍵要點

  • 使用 bg-gray-50 背景色
  • 序號欄位固定寬度 w-[50px] 並置中
  • 可排序欄位使用 <button> 包裹,加上 hover 效果
  • 操作欄位置中顯示

3.3 排序圖標元件

const SortIcon = ({ field }: { field: string }) => {
  if (sortField !== field) {
    return <ArrowUpDown className="h-4 w-4 text-muted-foreground ml-1" />;
  }
  if (sortDirection === "asc") {
    return <ArrowUp className="h-4 w-4 text-primary ml-1" />;
  }
  if (sortDirection === "desc") {
    return <ArrowDown className="h-4 w-4 text-primary ml-1" />;
  }
  return <ArrowUpDown className="h-4 w-4 text-muted-foreground ml-1" />;
};

3.4 表格主體

<TableBody>
  {items.length === 0 ? (
    <TableRow>
      <TableCell colSpan={7} 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 justify-center gap-2">
            {/* 操作按鈕 */}
          </div>
        </TableCell>
      </TableRow>
    ))
  )}
</TableBody>

關鍵要點

  • 空狀態訊息使用置中、灰色文字
  • 序號欄使用 text-gray-500 font-medium text-center
  • 操作欄使用 flex justify-center gap-2 排列按鈕

3.5 操作欄按鈕組

<TableCell className="text-center">
  <div className="flex justify-center gap-2">
    <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>
        {/* 刪除確認對話框 */}
      </AlertDialog>
    </Can>
  </div>
</TableCell>

4. 分頁規範

4.1 統一分頁元件

使用 @/Components/shared/Pagination 元件:

import Pagination from "@/Components/shared/Pagination";

// 在表格下方
<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>

4.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 }
  );
};

5. Badge 與狀態顯示

5.1 基本 Badge

使用 @/Components/ui/badge

import { Badge } from "@/Components/ui/badge";

// Outline 樣式(最常用)
<Badge variant="outline">
  {item.category?.name || '-'}
</Badge>

// 預設樣式(主題色背景)
<Badge variant="default">啟用中</Badge>

// 錯誤樣式
<Badge variant="destructive">停用</Badge>

5.2 角色顯示(特殊樣式)

參考使用者管理的角色顯示模式:

<div className="flex flex-wrap gap-2">
  {user.roles.map(role => (
    <div
      key={role.id}
      className={cn(
        "inline-flex flex-col px-3 py-1.5 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">
        {role.name === 'super-admin' && <Shield className="h-3 w-3 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>
      <span className="text-[10px] text-gray-500 font-mono">
        {role.name}
      </span>
    </div>
  ))}
</div>

6. 頁面佈局規範

6.1 標準頁面頭部

<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>

6.2 容器寬度

<div className="container mx-auto p-6 max-w-7xl">
  {/* 頁面內容 */}
</div>

7. 權限控制規範

7.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>

7.2 權限命名規範

遵循 resource.action 格式:

  • resource.index:查看列表
  • resource.show:查看詳情
  • resource.create:新增
  • resource.edit:編輯
  • resource.delete:刪除

8. 通知訊息規範

8.1 使用 Toast 通知

使用 sonnertoast 進行通知:

import { toast } from 'sonner';

// 成功訊息
toast.success('操作成功');

// 錯誤訊息
toast.error('操作失敗');

// 資訊訊息
toast.info('提示訊息');

// 警告訊息
toast.warning('警告訊息');

8.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('刪除失敗,請檢查權限'),
});

9. 顏色系統

9.1 主題色

參考 resources/css/app.css 中的色彩定義:

--primary-main: #01ab83;      /* 主題綠色 */
--primary-dark: #018a6a;      /* 深綠色 */
--primary-light: #33bc9a;     /* 淺綠色 */
--primary-lightest: #e6f7f3;  /* 最淺綠色背景 */

--grey-0: #1a1a1a;            /* 深黑色文字 */
--grey-1: #4a4a4a;            /* 深灰色文字 */
--grey-2: #6b6b6b;            /* 中灰色文字 */
--grey-3: #9e9e9e;            /* 淺灰色文字 */
--grey-4: #e0e0e0;            /* 邊框灰色 */
--grey-5: #fff;               /* 白色 */

9.2 狀態色

--other-success: #01ab83;     /* 成功(同主題色)*/
--other-error: #dc2626;       /* 錯誤紅色 */
--other-warning: #f59e0b;     /* 警告橙色 */
--other-info: #3b82f6;        /* 資訊藍色 */

10. 檢查清單

在開發或審查頁面時,請確認以下項目:

按鈕

  • 使用 button-filled-*button-outlined-* 類別
  • 主要操作使用 button-filled-primary
  • 編輯操作使用 button-outlined-primary
  • 刪除操作使用 button-outlined-error
  • 按鈕尺寸正確sm/default/lg
  • 包含適當的圖標(左側或單一圖標)

圖標

  • 全部使用 lucide-react
  • 尺寸正確h-3/h-4/h-6 w-3/w-4/w-6
  • 顏色與上下文一致
  • 有明確的語義(編輯=Pencil、刪除=Trash2 等)

表格

  • 使用 @/Components/ui/table 元件
  • bg-white rounded-lg shadow-sm border 容器
  • 標題列有 bg-gray-50 背景
  • 序號欄固定寬度並置中
  • 操作欄使用 flex justify-center gap-2
  • 空狀態訊息置中顯示
  • 可排序欄位有排序圖標

分頁

  • 使用 @/Components/shared/Pagination
  • 有每頁筆數選擇器10/20/50/100
  • 佈局為 flex justify-between

權限

  • 所有操作按鈕都用 <Can> 包裹
  • 權限命名符合 resource.action 格式

通知

  • 使用 toast 提供操作反饋
  • 成功/錯誤訊息明確

整體

  • 頁面有標準頭部(標題 + 圖標 + 說明 + 新增按鈕)
  • 容器寬度使用 max-w-7xl
  • 色彩使用符合主題

11. 常見錯誤與修正

錯誤:自定義按鈕樣式

// 錯誤
<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';  // ❌ 不使用 react-icons

<FaEdit />
// 正確
import { Pencil } from 'lucide-react';  // ✅ 使用 lucide-react

<Pencil className="h-4 w-4" />

錯誤:操作欄未置中

// 錯誤
<TableCell>
  <Button>編輯</Button>
  <Button>刪除</Button>
</TableCell>
// 正確
<TableCell className="text-center">
  <div className="flex 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>

12. 實際範例

參考以下頁面作為標準實作:

  • 使用者管理resources/js/Pages/Admin/User/Index.tsx
  • 產品管理resources/js/Pages/Product/Index.tsx
  • 產品表格resources/js/Components/Product/ProductTable.tsx

這些頁面展示了完整的 UI 統一性實踐,包括按鈕、圖標、表格、分頁、權限控制等所有元素。


總結

遵循本規範可確保:

  1. 視覺一致性:所有頁面看起來像同一個系統
  2. 維護效率:使用統一組件,修改一處即可影響全局
  3. 開發速度:有明確的模式可循,減少決策時間
  4. 使用者體驗:一致的互動模式降低學習成本

當你在開發或審查 koori-erp 的 UI 時,請務必參考此規範,確保每個元件都符合既定的標準。