跳至主要内容

UX 設計指南

目標讀者: 前端開發者、UI/UX 設計師、AI 助手

本文檔定義參考實作(src/)應遵循的使用者體驗設計原則。這些原則確保應用程式具有一致、直覺的互動體驗。


目錄

  1. 錯誤處理策略
  2. 使用者通知
  3. 確認對話框
  4. 對話框內容規範
  5. 列表元素規範
  6. 頁面佈局與捲動
  7. 載入狀態
  8. 空狀態
  9. 表單互動
  10. 狀態回饋
  11. 頁面標題列按鈕

1. 錯誤處理策略

設計原則

優先使用 Inline Alert,無表單時才使用阻塞式對話框

錯誤訊息的顯示位置應該讓使用者能夠:

  1. 立即理解問題所在
  2. 方便地修正錯誤
  3. 不被額外的對話框打斷

錯誤處理決策表

情境處理方式原因
表單頁面(Editor)Inline Alert用戶可直接修正後重試
對話框內有表單Dialog 內 Inline Alert避免對話框疊對話框
無表單操作(列表動作)prompt.error()操作已觸發,需確保用戶知悉

實作範例

情境一:表單頁面錯誤

function OrderEditor() {
const [error, setError] = useState<string | null>(null)

const handleSubmit = async (data: FormData) => {
try {
setError(null)
await orderService.create(data)
prompt.success(t('Order created successfully'))
navigate('/orders')
} catch (err) {
// 錯誤顯示在表單上方
setError(err instanceof ErrorResponse ? err.message : t('Failed to create order'))
}
}

return (
<form onSubmit={handleSubmit}>
{/* Inline Alert */}
{error && (
<div className="alert alert-error mb-4">
<span>{error}</span>
</div>
)}
{/* 表單內容 */}
</form>
)
}

情境二:對話框內有表單

// 父組件
function CustomerFinder() {
const [deactivateError, setDeactivateError] = useState<string | null>(null)

const handleConfirmDeactivate = async (reason: string) => {
try {
setDeactivateError(null)
await customerService.deactivate(id, reason)
prompt.success(t('Customer deactivated'))
closeDialog()
} catch (err) {
// 錯誤顯示在對話框內,而非 prompt.error()
setDeactivateError(err instanceof ErrorResponse ? err.message : t('Failed'))
}
}

return (
<DeactivateDialog
error={deactivateError}
onConfirm={handleConfirmDeactivate}
onCancel={() => {
setDeactivateError(null)
closeDialog()
}}
/>
)
}

// 對話框組件
function DeactivateDialog({ error, onConfirm, onCancel }) {
return (
<Dialog>
{/* Inline Alert 在對話框內 */}
{error && (
<div className="alert alert-error mb-4">
<span>{error}</span>
</div>
)}
{/* 表單內容 */}
</Dialog>
)
}

情境三:無表單的列表操作

// 列表頁面的快速操作(無對話框承載錯誤)
const handleQuickDelete = async (id: string) => {
try {
await orderService.delete(id)
prompt.success(t('Order deleted'))
} catch (err) {
// 沒有對話框,使用 prompt.error()
prompt.error(err instanceof ErrorResponse ? err.message : t('Delete failed'))
}
}

錯誤清除時機

  • 開啟對話框時:清除之前的錯誤
  • 重新提交時:清除錯誤後再執行
  • 關閉對話框時:清除錯誤

2. 使用者通知

設計原則

使用 prompt API,禁止使用原生 alert/confirm

訊息類型方法顯示方式使用時機
成功prompt.success()Toast(自動消失)操作成功完成
資訊prompt.info()Toast一般資訊提示
錯誤prompt.error()阻塞式對話框無表單時的錯誤
警告prompt.warn()阻塞式對話框需要用戶確認的警告
確認prompt.confirm()阻塞式對話框需要用戶做選擇

禁止使用

// ❌ 禁止
alert('Success!')
confirm('Delete this?')
window.alert()
window.confirm()

// ✅ 使用 prompt API
prompt.success('Success!')
await prompt.confirm('Delete this?', { actions: ['delete', 'cancel'] })

訊息內容原則

  1. 簡潔明確:用最少的文字傳達資訊
  2. 使用 i18n:所有訊息都要經過翻譯 t('...')
  3. 動態參數:使用模板語法 t('Order ${orderNumber} created', { orderNumber })

3. 確認對話框

設計原則

按鈕觸發的確認優先使用 Button 內建屬性

Button 內建確認(推薦)

// 危險操作(紅色警告)
<Button
warning={t('Are you sure to delete? This cannot be undone.')}
onClick={handleDelete}
>
{t('Delete')}
</Button>

// 一般確認(藍色資訊)
<Button
confirmation={t('Confirm this action?')}
onClick={handleAction}
>
{t('Execute')}
</Button>

優點

  • 程式碼簡潔,無需 async/await
  • 確認邏輯與按鈕綁定
  • warning 優先級高於 confirmation

prompt.confirm(次選)

僅在非按鈕觸發時使用:

// 選單項目、鍵盤快捷鍵等非按鈕觸發
const handleMenuAction = async () => {
const answer = await prompt.confirm(t('Confirm?'), {
actions: ['yes', 'no']
})
if (answer === 'yes') {
// 執行操作
}
}

4. 對話框內容規範

設計原則

對話框內所有元素應保持一致的視覺語言(comfortable density)

使用透明度區分資訊層次,而非縮小字體大小。這確保:

  1. 一致性:同一對話框內的元素有統一的視覺風格
  2. 可讀性:說明文字雖為次要資訊,但用戶仍需清楚理解操作後果
  3. 層次分明:透過透明度(如 /70)區分主次資訊

字體大小規範

元素樣式說明
標題text-lg font-semibold text-base-content最突出
說明文字text-base text-base-content/70透明度區分次要
表單標籤text-base text-base-content與表單元素一致
表單元素comfortable density (text-base)主要互動區域
按鈕comfortable density (text-base)主要動作
輔助資訊text-base text-base-content/60更次要的提示

錯誤範例

// ❌ 錯誤:混用 text-sm 和 text-base
<Description className="text-sm text-base-content/70"> // 說明用 text-sm
{t('Are you sure?')}
</Description>
<RadioGroup>
<Label className="text-sm">...</Label> // 標籤用 text-sm
</RadioGroup>
<Button>{t('Confirm')}</Button> // 按鈕用 text-base (comfortable)

正確範例

// ✅ 正確:統一使用 text-base,透明度區分層次
<Description className="text-base text-base-content/70"> // 透明度 70%
{t('Are you sure?')}
</Description>
<RadioGroup>
<Label className="text-base text-base-content">...</Label> // 完整透明度
</RadioGroup>
<Button>{t('Confirm')}</Button> // comfortable density

完整對話框範例

<Dialog>
{/* Header */}
<div className="flex items-center gap-2 p-4 border-b">
<AlertTriangle className="w-5 h-5 text-warning" />
<DialogTitle className="text-lg font-semibold text-base-content">
{t('Confirm Action')}
</DialogTitle>
</div>

{/* Content */}
<div className="p-4">
{/* 說明文字:text-base + 透明度 */}
<Description className="text-base text-base-content/70 mb-4">
{t('This action cannot be undone.')}
</Description>

{/* 表單元素:text-base */}
<RadioGroup>
<Radio>
<Label className="text-base text-base-content">
{t('Option 1')}
</Label>
</Radio>
</RadioGroup>

{/* 輔助資訊:text-base + 更低透明度 */}
<p className="text-base text-base-content/60 mt-2">
{t('Select an option to continue.')}
</p>
</div>

{/* Actions */}
<div className="flex justify-end gap-2 p-4">
<Button variant="soft">{t('Cancel')}</Button>
<Button color="primary">{t('Confirm')}</Button>
</div>
</Dialog>

5. 列表元素規範

設計原則

列表頁面中的元素應保持一致的視覺語言

所有 Finder(列表頁面)中的 Badge 和識別碼應遵循統一的樣式規範,確保跨模組的視覺一致性。

Badge 規範

所有狀態標籤統一使用 DaisyUI 的 badge-md 尺寸:

// ✅ 正確:使用 badge-md
<span className="badge badge-success badge-md whitespace-nowrap">
{t('Active')}
</span>

// ❌ 錯誤:使用 badge-sm 或自訂 padding
<span className="badge badge-success badge-sm">...</span>
<span className="badge badge-success text-sm px-4 py-2">...</span>
屬性說明
尺寸badge-mdDaisyUI 中等尺寸,視覺平衡
換行whitespace-nowrap防止文字換行
顏色badge-{color}依語意選擇顏色

Badge 欄位對齊

在列表(VirtualTable)中,Badge 欄位應設定為置中對齊,使不同長度的 Badge 看起來更整齊:

// ✅ 正確:Badge 欄位置中對齊
{
accessorKey: 'status',
header: t('Status'),
cell: ({ row }) => <StatusBadge status={row.original.status} />,
meta: { align: 'center' }, // Badge 欄位置中
enableSorting: false,
}

// ❌ 錯誤:Badge 欄位左對齊(預設)
{
accessorKey: 'status',
header: t('Status'),
cell: ({ row }) => <StatusBadge status={row.original.status} />,
// 缺少 meta: { align: 'center' }
}

Badge 顏色語意

狀態類型Badge Class使用場景
成功/啟用badge-success已完成、啟用、上架
警告/待處理badge-warning待確認、低庫存
錯誤/停用badge-error已取消、停用、缺貨
資訊/進行中badge-info已確認、進行中
主要badge-primary設計中
次要badge-secondary待出貨、VVIP
強調badge-accent配送中
中性badge-neutral一般、預設

啟用/停用狀態規範

所有模組的啟用/停用狀態統一使用以下配色:

狀態Badge Class說明
啟用 (active)badge-success綠色,表示正常運作
停用 (inactive)badge-error紅色,表示已停用

注意:不使用 badge-ghost 作為停用狀態,因為透明樣式與其他狀態視覺差異過大,造成不一致感。

識別碼規範

訂單編號、客戶編號、商品 SKU 等識別碼統一使用等寬字體:

// ✅ 正確:使用 font-mono,無 text-sm
<Link to={id} className="link link-hover whitespace-nowrap font-mono">
{orderNumber}
</Link>

// ❌ 錯誤:額外添加 text-sm 或缺少 font-mono
<Link className="link link-hover whitespace-nowrap font-mono text-sm">...</Link>
<Link className="link link-hover whitespace-nowrap">...</Link>
屬性說明
字體font-mono等寬字體,適合編號對齊
連結樣式link link-hover可點擊的連結樣式
換行whitespace-nowrap防止編號換行
字體大小繼承預設不額外設定 text-sm

參考實作

模組Badge 組件識別碼 Cell
訂單管理OrderStatusBadgeOrderNumberCell
客戶管理CustomerTierBadge, CustomerStatusBadgeCustomerNumberCell
商品管理ProductCategoryBadge, ProductStatusBadgeProductSkuCell

注意:庫存狀態使用 StockStatusIndicator(彩色圓點),而非 Badge。這是因為庫存欄位包含數字+狀態,使用圓點可確保靠右對齊時視覺整齊。


6. 頁面佈局與捲動

設計原則

使用固定高度容器 + 內容區捲動,而非整頁捲動

這確保:

  1. 固定 Header:Header 永遠可見,不隨內容捲動
  2. 獨立捲動區域:每個內容區域可以獨立捲動
  3. 視覺一致性:所有頁面使用相同的佈局模式

佈局架構

應用程式使用三層佈局架構:

┌─────────────────────────────────────────────┐
│ MainLayout (h-screen overflow-hidden) │
│ ┌─────────────────────────────────────────┐ │
│ │ Header (固定高度) │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ main (flex-1 overflow-hidden h-full) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ AppletShell (h-full overflow-hidden)│ │ │
│ │ │ ┌──────┐ ┌────────────────────────┐ │ │ │
│ │ │ │aside │ │ main │ │ │ │
│ │ │ │ │ │ (flex-1 overflow-y-auto)│ │ │ │
│ │ │ │捲動 │ │ ↕ 內容區域可以捲動 │ │ │ │
│ │ │ └──────┘ └────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ HomePage (h-full overflow-y-auto) │ │ │
│ │ │ ↕ 獨立頁面可以捲動 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

三層架構說明

  1. MainLayout 層(最外層)

    • 使用 h-screen overflow-hidden 固定視窗高度
    • Header 固定在頂部,不隨內容捲動
    • main 區域使用 flex-1 overflow-hidden h-full 佔據剩餘空間
  2. AppletShell 層(Applet 容器)

    • 使用 h-full overflow-hidden 佔滿父容器高度
    • aside(側邊工具列)使用 overflow-y-auto 可獨立捲動
    • main(內容區域)使用 flex-1 overflow-y-auto 可獨立捲動
  3. 內容層(頁面內容)

    • Applet 頁面:位於 AppletShell 的 main 區域中
    • 獨立頁面(如 HomePage):直接位於 MainLayout 的 main 區域,使用 h-full overflow-y-auto

Applet 頁面佈局

Applet 頁面(Finder、Detail、Editor)位於 AppletShell 的 main 區域中,不需要額外設定高度或 overflow:

// ✅ 正確:Applet 頁面不設定高度
export function OrderFinder() {
return (
<div className="p-4">
{/* 頁面內容 */}
<VirtualTable />
</div>
)
}

AppletShell 結構(參考 src/components/applet-shell/applet-shell.tsx):

<div className="flex h-full overflow-hidden">
{/* 側邊工具列:可獨立捲動 */}
<aside className="overflow-y-auto">
{/* 工具列內容 */}
</aside>

{/* 內容區域:可獨立捲動 */}
<main className="flex-1 overflow-y-auto">
{children} {/* Applet 頁面放這裡 */}
</main>
</div>

獨立頁面佈局

獨立頁面(如 HomePage)直接位於 MainLayout 的 main 區域,必須設定 h-full overflow-y-auto

// ✅ 正確:獨立頁面使用 h-full overflow-y-auto
export function HomePage() {
return (
<div className="h-full overflow-y-auto bg-base-200">
<div className="p-6 md:p-8">
{/* 頁面內容 */}
</div>
</div>
)
}

為什麼需要 h-full overflow-y-auto

  • h-full:佔滿父容器(MainLayout 的 main)高度
  • overflow-y-auto:當內容超出高度時,啟用垂直捲動
  • 內層 <div className="p-6 md:p-8">:提供內容的 padding

常見錯誤

錯誤 1:使用 min-h-screen

// ❌ 錯誤:min-h-screen 會導致容器無法正確計算高度
export function HomePage() {
return (
<div className="min-h-screen bg-base-200 p-6">
{/* 當內容超出螢幕高度時,容器會繼續向下延伸 */}
{/* 導致 MainLayout 的 main 區域無法啟用捲動 */}
</div>
)
}

問題min-h-screen 設定最小高度為 100vh,當內容超出時,容器會繼續向下延伸,而非啟用捲動。

解決方案:使用 h-full overflow-y-auto 代替 min-h-screen

錯誤 2:在 Applet 頁面設定高度

// ❌ 錯誤:Applet 頁面不應設定高度
export function OrderFinder() {
return (
<div className="h-full overflow-y-auto p-4">
{/* AppletShell 已經處理捲動,這裡不需要 */}
</div>
)
}

問題:AppletShell 的 main 區域已經設定 overflow-y-auto,Applet 頁面不需要額外設定。

解決方案:Applet 頁面只設定 padding 和其他樣式,不設定高度或 overflow。

錯誤 3:缺少外層捲動容器

// ❌ 錯誤:獨立頁面缺少捲動容器
export function HomePage() {
return (
<div className="p-6 md:p-8 bg-base-200">
{/* 缺少 h-full overflow-y-auto,內容超出時無法捲動 */}
</div>
)
}

問題:缺少 h-full overflow-y-auto 的外層容器,導致內容超出時無法捲動。

解決方案

// ✅ 正確:添加外層捲動容器
export function HomePage() {
return (
<div className="h-full overflow-y-auto bg-base-200">
<div className="p-6 md:p-8">
{/* 內容 */}
</div>
</div>
)
}

參考實作

佈局類型參考檔案說明
MainLayoutsrc/layouts/main-layout.tsx最外層佈局,h-screen overflow-hidden
AppletShellsrc/components/applet-shell/applet-shell.tsxApplet 容器,h-full overflow-hidden
Applet 頁面src/applets/order-applet/order-finder.tsx無需設定高度或 overflow
獨立頁面src/pages/home-page.tsx使用 h-full overflow-y-auto

7. 載入狀態

設計原則

  1. 即時回饋:操作開始後立即顯示載入狀態
  2. 禁用互動:載入期間禁用相關按鈕和輸入
  3. 視覺一致:使用統一的載入指示器

頁面載入

if (isLoading) {
return (
<div className="flex items-center justify-center h-full">
<span className="loading loading-spinner loading-lg text-primary"></span>
</div>
)
}

按鈕載入狀態

<Button
disabled={isSubmitting}
onClick={handleSubmit}
>
{isSubmitting ? (
<>
<span className="loading loading-spinner loading-xs"></span>
{t('Processing...')}
</>
) : (
t('Submit')
)}
</Button>

對話框載入

對話框操作期間:

  • 所有按鈕設為 disabled
  • 關閉按鈕(X)設為 disabled
  • 顯示載入指示器
<Dialog>
<button disabled={isLoading} onClick={onClose}>
<X className="h-4 w-4" />
</button>

<Button disabled={isLoading}>
{isLoading ? (
<>
<span className="loading loading-spinner loading-xs"></span>
{t('Processing...')}
</>
) : (
t('Confirm')
)}
</Button>
</Dialog>

8. 空狀態

設計原則

  1. 說明情況:清楚告知為什麼沒有資料
  2. 引導行動:提供下一步操作建議
  3. 視覺友善:使用圖示增加親和力

列表空狀態

{orders.length === 0 && (
<div className="text-center py-12">
<ShoppingBag className="mx-auto h-12 w-12 text-base-content/30" />
<h3 className="mt-2 text-sm font-semibold text-base-content">
{t('No orders')}
</h3>
<p className="mt-1 text-sm text-base-content/60">
{t('Get started by creating a new order.')}
</p>
<div className="mt-6">
<Button onClick={() => navigate('/orders/new')}>
{t('Create Order')}
</Button>
</div>
</div>
)}

搜尋無結果

{searchResults.length === 0 && searchTerm && (
<div className="text-center py-8">
<Search className="mx-auto h-8 w-8 text-base-content/30" />
<p className="mt-2 text-sm text-base-content/60">
{t('No results found for "${term}"', { term: searchTerm })}
</p>
<p className="text-sm text-base-content/40">
{t('Try adjusting your search or filters.')}
</p>
</div>
)}

工作台空狀態(待辦清空)

{pendingOrders.length === 0 && (
<div className="text-center py-12">
<CheckCircle className="mx-auto h-12 w-12 text-success" />
<h3 className="mt-2 text-lg font-semibold text-base-content">
{t('All done!')}
</h3>
<p className="mt-1 text-sm text-base-content/60">
{t('All orders have been processed.')}
</p>
</div>
)}

9. 表單互動

設計原則

  1. 即時驗證:欄位失焦時進行驗證
  2. 清楚標示:必填欄位標示星號 *
  3. 錯誤定位:錯誤訊息顯示在對應欄位下方
  4. 保護資料:離開前提示未儲存變更

欄位驗證錯誤

<Input
label={t('Email')}
required
error={errors.email?.message}
/>

// 錯誤訊息會顯示在欄位下方
// ⚠ 請輸入有效的電子郵件地址

未儲存變更警告

// 使用 Button 的 warning 屬性
<Button
variant="soft"
warning={hasDraft ? t('Discard unsaved changes?') : undefined}
onClick={() => navigate(-1)}
>
{t('Cancel')}
</Button>

表單提交狀態

<div className="flex gap-2">
<Button
variant="soft"
disabled={isSubmitting}
onClick={handleCancel}
>
{t('Cancel')}
</Button>
<Button
type="submit"
disabled={isSubmitting || !isValid}
>
{isSubmitting ? (
<>
<span className="loading loading-spinner loading-xs"></span>
{t('Saving...')}
</>
) : (
<>
<Save className="w-4 h-4" />
{t('Save')}
</>
)}
</Button>
</div>

10. 狀態回饋

設計原則

  1. 視覺化進度:使用進度條顯示流程階段
  2. 顏色語意
    • 綠色:已完成
    • 藍色:當前階段
    • 灰色:未開始
    • 紅色:錯誤/取消
  3. 歷史紀錄:提供可追溯的狀態變更歷史

狀態進度條

[✓] 待確認 → [✓] 已確認 → [●] 設計中 → [ ] 待出貨 → [ ] 配送中 → [ ] 已完成
綠色 綠色 藍色高亮 灰色 灰色 灰色

狀態標籤顏色

狀態類型DaisyUI Class使用場景
待處理badge-warning待確認、待出貨
進行中badge-info設計中、配送中
已完成badge-success已完成、已簽收
已取消badge-error已取消、配送失敗

狀態操作按鈕

根據當前狀態動態顯示可執行的操作:

// 只顯示當前狀態允許的操作
{canConfirm && (
<Button onClick={handleConfirm}>
{t('Confirm Order')}
</Button>
)}

{canCancel && (
<Button
color="error"
warning={t('Are you sure to cancel this order?')}
onClick={handleCancel}
>
{t('Cancel Order')}
</Button>
)}

11. 頁面標題列按鈕

設計原則

頁面標題列右側的操作按鈕應該遵循一致的樣式和排列規則,確保使用者能夠快速識別和操作。

按鈕樣式規範

所有標題列按鈕使用統一樣式:

// 主要操作按鈕(純文字)
<Button
variant="soft"
density="tight"
className="text-base"
color="primary"
>
{t('Save')}
</Button>

// 圖示按鈕(更多選單、關閉)
<Button
variant="soft"
density="tight"
className="text-base"
shape="square"
>
<Icon className="w-4 h-4" />
</Button>
屬性說明
variantsoft柔和風格,hover 時填滿背景
densitytight緊湊高度 (32px),適合標題列
classNametext-base字體大小與頁面內容一致 (16px)

按鈕類型

  • 主要操作:純文字,無圖示(簡潔明確)
  • 更多選單shape="square" + MoreVertical 圖示
  • 關閉按鈕shape="square" + X 圖示

按鈕顏色語意

操作類型color範例
主要操作primary儲存、編輯、新增
成功操作success確認、啟用、建立並確認
警告操作warning取消訂單、停用
危險操作error刪除
一般操作無 (default)重新整理、關閉

按鈕排列順序

從左到右的排列順序:

[主要操作] [次要操作...] [關閉按鈕]
  1. 主要操作:頁面最重要的操作(儲存、建立等)
  2. 次要操作:其他可用操作(編輯、取消等)
  3. 關閉按鈕:永遠在最右側

關閉按鈕

關閉按鈕使用 shape="square" 且只顯示圖示:

<Button
variant="soft"
density="tight"
className="text-base"
shape="square"
title={t('Close')}
onClick={handleClose}
>
<X className="size-4" />
</Button>

按鈕數量控制

當按鈕過多時,使用「更多操作」選單收納次要操作:

按鈕數量處理方式
≤ 3 個全部顯示
> 3 個保留 1-2 個主要操作 + 更多選單 + 關閉按鈕

更多選單範例

<div className="flex">
{/* 主要操作(純文字) */}
<Button variant="soft" density="tight" className="text-base" color="primary">
{t('Save')}
</Button>

{/* 更多操作選單 */}
<Dropdown align="end">
<Dropdown.Trigger>
<Button variant="soft" density="tight" className="text-base" shape="square" title={t('More actions')}>
<MoreVertical className="w-4 h-4" />
</Button>
</Dropdown.Trigger>
<Dropdown.Content>
<Dropdown.Item onClick={handleCancel}>
<XCircle className="w-4 h-4" />
{t('Cancel Order')}
</Dropdown.Item>
<Dropdown.Item color="error" onClick={handleDelete}>
<Trash2 className="w-4 h-4" />
{t('Delete')}
</Dropdown.Item>
</Dropdown.Content>
</Dropdown>

{/* 關閉按鈕 */}
<Button variant="soft" density="tight" className="text-base" shape="square">
<X className="size-4" />
</Button>
</div>

Responsive 設計

桌面版與手機版採用相同的設計模式,只保留一個主要操作按鈕,其他收進選單:

螢幕大小顯示方式
桌面版 (sm+)[主要操作] [更多選單 ⋮] [關閉]
手機版[主要操作] [更多選單 ⋮] [關閉]

這樣的好處:

  • 介面一致,使用者不需要適應不同的操作方式
  • 標題列保持簡潔
  • 次要操作統一收納在選單中

容器樣式

按鈕容器使用 flex,不需要 gap(soft variant 按鈕之間自然分隔):

<div className="flex items-center justify-between">
<h1>...</h1>
<div className="flex">
{/* 按鈕 */}
</div>
</div>

參考實作

以下是遵循本指南的參考實作:

模式參考檔案
表單錯誤處理src/applets/order-applet/order-editor.tsx
對話框內表單錯誤src/applets/order-applet/components/cancel-order-dialog.tsx
確認對話框src/applets/order-applet/order-detail.tsx
載入狀態src/applets/customer-applet/customer-detail.tsx
空狀態src/applets/sales-applet/sales-applet.tsx
狀態進度條src/applets/order-applet/components/order-status-progress.tsx
頁面標題列按鈕src/applets/order-applet/order-detail.tsx
表格操作欄選單src/applets/order-applet/order-finder.tsx

相關文件


最後更新: 2025-12-21