跳至主要内容

訊息系統

內容來源

本頁內容源自 appfuse-web/lib/messaging/README.md,如有差異以 README 為準。

AppFuse Web 提供全域訊息通知系統,包含 Toast(快顯通知)和 Dialog(對話框)兩種形式。

設計理念

  • Toast: 非阻斷式通知,自動消失
  • Dialog: 阻斷式對話框,需使用者操作

快速開始

1. 設置元件

在應用程式根元件加入 Toast 和 Dialog 容器:

import { MessageToast, MessageDialog } from '@appfuse/appfuse-web/messaging';

function App() {
return (
<>
<RouterProvider router={router} />
<MessageToast />
<MessageDialog />
</>
);
}

2. 使用 prompt API

import { prompt } from '@appfuse/appfuse-web/messaging';

// 成功訊息(Toast,自動消失)
prompt.success('Data saved successfully');

// 資訊對話框
prompt.info('Please note this important information');

// 錯誤對話框(需確認)
prompt.error('Operation failed. Please try again.');

// 警告對話框(Yes/No)
prompt.warn('This action cannot be undone');

// 確認對話框(回傳 Promise)
const confirmed = await prompt.confirm('Delete this item?');
if (confirmed) {
// 執行刪除
}

API 參考

prompt.success(message)

顯示成功 Toast,3 秒後自動消失。

prompt.success('Data saved');
prompt.success('File uploaded successfully');

使用時機: 操作成功後的正向回饋

prompt.info(message)

顯示資訊對話框,需點擊關閉。

prompt.info('Your session will expire in 5 minutes');

使用時機: 需要使用者注意但非錯誤的資訊

prompt.error(message)

顯示錯誤對話框,需點擊確認。

prompt.error('Failed to save data. Please check your connection.');

使用時機: 操作失敗,需要使用者知曉

prompt.warn(message)

顯示警告對話框,帶有 Yes/No 按鈕。

const proceed = await prompt.warn('This will overwrite existing data');
if (proceed) {
// 繼續操作
}

使用時機: 警告使用者潛在風險,但允許繼續

prompt.confirm(message, options?)

顯示確認對話框,回傳 Promise。

// 基本用法
const confirmed = await prompt.confirm('Delete this item?');

// 自訂按鈕文字
const confirmed = await prompt.confirm('Discard changes?', {
confirmText: 'Discard',
cancelText: 'Keep editing',
});

使用時機: 需要使用者明確確認的操作

訊息類型比較

類型元件自動消失需操作回傳值
successToast✅ 3秒void
infoDialog✅ 關閉void
errorDialog✅ 確認void
warnDialog✅ Yes/NoPromise<boolean>
confirmDialog✅ 確認/取消Promise<boolean>

進階用法

自動訊息去重

系統會自動去除 3 秒內的重複訊息:

// 快速連點按鈕時,只會顯示一次
button.onClick = () => {
prompt.success('Saved'); // 只顯示一次
};

訊息自動翻譯

訊息內容會自動翻譯(如果有對應的 i18n 資源):

// 傳入英文 key
prompt.success('Data saved');

// 如果 i18n 有定義,會顯示翻譯後的文字
// "資料已儲存"

在服務層使用

// services/userService.ts
import { prompt } from '@appfuse/appfuse-web/messaging';

export async function deleteUser(id: string) {
const confirmed = await prompt.confirm('Delete this user?');
if (!confirmed) return false;

try {
await http.delete(`/users/${id}`);
prompt.success('User deleted');
return true;
} catch (error) {
prompt.error('Failed to delete user');
return false;
}
}

表單提交回饋

function UserForm() {
const onSubmit = async (data: FormData) => {
try {
await userService.create(data);
prompt.success('User created');
navigate('/users');
} catch (error) {
if (error.response?.status === 409) {
prompt.error('Email already exists');
} else {
prompt.error('Failed to create user');
}
}
};

return <form onSubmit={handleSubmit(onSubmit)}>...</form>;
}

離開頁面確認

import { useBlocker } from 'react-router-dom';

function EditForm() {
const { isDirty } = useFormState();

useBlocker(async ({ proceed, reset }) => {
if (!isDirty) {
proceed();
return;
}

const confirmed = await prompt.confirm('Discard unsaved changes?');
if (confirmed) {
proceed();
} else {
reset();
}
});

return <form>...</form>;
}

Thread 隔離

每個訊息可指定 thread,實現多頁面訊息隔離(避免 A 頁面的訊息出現在 B 頁面):

// 發送時指定 thread
prompt.success('Data saved', { thread: '/orders' })

// 接收端只顯示對應 thread 的訊息
<MessageToast currentPath="/orders" />

通用 UI 元件

如果不需要 prompt 服務,可直接使用低階的 Toast / Dialog 元件自行管理狀態:

import { Toast, ToastContainer, Dialog } from '@appfuse/appfuse-web/components'

<ToastContainer position="top-right">
<Toast id={1} message="自訂訊息" severity="success" onClose={handleClose} />
</ToastContainer>

<Dialog
open={isOpen}
title="確認"
content="確定要執行嗎?"
severity="warning"
actions={['確定', '取消']}
onAction={handleAction}
onClose={() => setIsOpen(false)}
/>

樣式客製化

Toast 位置

MessageToast 預設顯示在右上角,可透過 props 調整:

<MessageToast position="top-right" />   {/* 預設 */}
<MessageToast position="top-left" />
<MessageToast position="bottom-right" />
<MessageToast position="bottom-left" />

Dialog 樣式

Dialog 使用 DaisyUI modal 樣式,會自動跟隨主題:

// 不同訊息類型使用不同顏色
// success: 綠色
// info: 藍色
// warn: 橘色
// error: 紅色

UX 最佳實踐

選擇正確的訊息類型

// ✅ 正確
prompt.success('Saved'); // 操作成功,不需注意
prompt.info('Session expires in 5 min'); // 需要注意的資訊
prompt.error('Network error'); // 操作失敗
prompt.confirm('Delete?'); // 需要確認的操作

// ❌ 錯誤
prompt.error('Data saved'); // 成功訊息不應用 error
prompt.success('Failed to save'); // 失敗訊息不應用 success

訊息內容準則

// ✅ 好的訊息
prompt.success('Order #123 created'); // 具體、簡潔
prompt.error('Unable to connect to server'); // 說明問題

// ❌ 不好的訊息
prompt.success('Success'); // 太籠統
prompt.error('Error'); // 沒有說明問題
prompt.error('An unexpected error occurred in the system while processing your request'); // 太長

避免過度使用

// ✅ 適當使用
async function saveForm(data) {
await api.save(data);
prompt.success('Saved'); // 只在最終結果顯示一次
}

// ❌ 過度使用
async function saveForm(data) {
prompt.info('Validating...'); // 不需要
await validate(data);
prompt.info('Saving...'); // 不需要
await api.save(data);
prompt.success('Saved');
}

下一步