# Toast 通知组件

一个轻量级的 Toast 通知系统，用于向用户展示临时消息。支持自定义类型（成功、错误、信息）、自动消失和流畅的动画效果。非常适合状态更新、错误消息和用户反馈。

## 安装

```bash
npm install @ticatec/uniface-element
```

## 导入

```typescript
import ToastBoard, { type Toast } from "@ticatec/uniface-element/ToastBoard";
```

## 基本用法

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  function showSuccessToast() {
    window.Toast.show("操作成功完成！", "success", 3);
  }
  
  function showErrorToast() {
    window.Toast.show("出现错误！", "error", 5);
  }
  
  function showInfoToast() {
    window.Toast.show("信息消息", "info");
  }
</script>

<!-- 在根布局中包含 ToastBoard 组件 -->
<ToastBoard />

<!-- 从任何地方触发 Toast -->
<button on:click={showSuccessToast}>显示成功</button>
<button on:click={showErrorToast}>显示错误</button>
<button on:click={showInfoToast}>显示信息</button>
```

## 属性 (ToastBoard)

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `fromBottom` | `boolean` | `false` | 是否从屏幕底部显示 Toast |

## 全局 Toast API

一旦 ToastBoard 被挂载，全局 `window.Toast` 对象即可使用：

### Toast.show()

```typescript
window.Toast.show(message: string, type?: "error" | "info" | "success", duration?: number)
```

| 参数 | 类型 | 默认值 | 描述 |
|-----------|------|---------|-------------|
| `message` | `string` | 必需 | 要显示的消息 |
| `type` | `"error" \| "info" \| "success"` | `"error"` | Toast 类型/样式 |
| `duration` | `number` | `3` | 自动消失前的持续时间（秒） |

## 类型

### 成功 Toast
- **颜色**：绿色背景 (`#2DC071`)
- **用途**：成功操作、确认
- **示例**： "数据保存成功！"

### 错误 Toast
- **颜色**：红色背景 (`#FF685B`)
- **用途**：错误消息、失败操作
- **示例**： "数据保存失败"

### 信息 Toast
- **颜色**：蓝色背景 (`#0061FF`)
- **用途**：一般信息、通知
- **示例**： "有新更新可用"

## 示例

### 基本实现

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  // 简单的 Toast 函数
  function success() {
    window.Toast.show("成功！数据已保存。", "success", 4);
  }
  
  function error() {
    window.Toast.show("错误！无法处理请求。", "error", 6);
  }
  
  function info() {
    window.Toast.show("信息：请检查您的电子邮件以获取更新。", "info", 3);
  }
</script>

<!-- 挂载 Toast 面板 -->
<ToastBoard />

<div class="demo">
  <h3>Toast 通知</h3>
  <button on:click={success}>成功 Toast</button>
  <button on:click={error}>错误 Toast</button>
  <button on:click={info}>信息 Toast</button>
</div>

<style>
  .demo {
    padding: 20px;
  }
  
  button {
    margin: 0 8px 8px 0;
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
</style>
```

### 表单验证与 Toast

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  let email = '';
  let password = '';
  let loading = false;
  
  async function handleSubmit() {
    if (!email) {
      window.Toast.show("电子邮件为必填项", "error", 4);
      return;
    }
    
    if (!email.includes('@')) {
      window.Toast.show("请输入有效的电子邮件地址", "error", 4);
      return;
    }
    
    if (!password) {
      window.Toast.show("密码为必填项", "error", 4);
      return;
    }
    
    if (password.length < 6) {
      window.Toast.show("密码必须至少包含 6 个字符", "error", 4);
      return;
    }
    
    loading = true;
    
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      if (Math.random() > 0.5) {
        window.Toast.show("登录成功！欢迎回来。", "success", 3);
        // 重定向或更新 UI
      } else {
        window.Toast.show("无效的凭据，请重试。", "error", 5);
      }
    } catch (error) {
      window.Toast.show("网络错误，请检查您的连接。", "error", 5);
    } finally {
      loading = false;
    }
  }
</script>

<ToastBoard />

<form on:submit|preventDefault={handleSubmit}>
  <h3>登录表单</h3>
  
  <div class="field">
    <label>电子邮件：</label>
    <input 
      type="email" 
      bind:value={email} 
      placeholder="请输入您的电子邮件" 
      disabled={loading}
    />
  </div>
  
  <div class="field">
    <label>密码：</label>
    <input 
      type="password" 
      bind:value={password} 
      placeholder="请输入您的密码" 
      disabled={loading}
    />
  </div>
  
  <button type="submit" disabled={loading}>
    {loading ? '登录中...' : '登录'}
  </button>
</form>

<style>
  form {
    max-width: 300px;
    margin: 20px;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .field {
    margin-bottom: 16px;
  }
  
  label {
    display: block;
    margin-bottom: 4px;
    font-weight: 500;
  }
  
  input {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-sizing: border-box;
  }
  
  button {
    width: 100%;
    padding: 10px;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
  
  button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }
</style>
```

### 文件上传与进度 Toast

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  let fileInput;
  let uploading = false;
  
  async function handleFileUpload(event) {
    const file = event.target.files[0];
    
    if (!file) {
      window.Toast.show("请选择要上传的文件", "error", 3);
      return;
    }
    
    // 文件大小验证（5MB 限制）
    if (file.size > 5 * 1024 * 1024) {
      window.Toast.show("文件大小必须小于 5MB", "error", 4);
      return;
    }
    
    // 文件类型验证
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(file.type)) {
      window.Toast.show("仅允许上传 JPEG、PNG 和 GIF 文件", "error", 4);
      return;
    }
    
    uploading = true;
    window.Toast.show("开始文件上传...", "info", 2);
    
    try {
      // 模拟上传进度
      await simulateUpload(file);
      
      window.Toast.show(`文件 "${file.name}" 上传成功！`, "success", 4);
      
      // 清空输入框
      fileInput.value = '';
      
    } catch (error) {
      window.Toast.show("上传失败，请重试。", "error", 5);
    } finally {
      uploading = false;
    }
  }
  
  async function simulateUpload(file) {
    // 模拟上传阶段
    await new Promise(resolve => setTimeout(resolve, 1000));
    window.Toast.show("处理文件中...", "info", 2);
    
    await new Promise(resolve => setTimeout(resolve, 1500));
    window.Toast.show("上传至服务器...", "info", 2);
    
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 随机失败模拟
    if (Math.random() < 0.2) {
      throw new Error("上传失败");
    }
  }
</script>

<ToastBoard />

<div class="upload-container">
  <h3>文件上传</h3>
  
  <div class="upload-area">
    <input 
      bind:this={fileInput}
      type="file" 
      accept="image/*" 
      on:change={handleFileUpload}
      disabled={uploading}
      id="file-upload"
    />
    <label for="file-upload" class:uploading>
      {uploading ? '⏳ 上传中...' : '📁 选择文件'}
    </label>
  </div>
  
  <p class="help-text">
    选择 JPEG、PNG 或 GIF 文件（最大 5MB）
  </p>
</div>

<style>
  .upload-container {
    max-width: 400px;
    margin: 20px;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .upload-area {
    margin: 16px 0;
  }
  
  input[type="file"] {
    display: none;
  }
  
  label {
    display: inline-block;
    padding: 12px 24px;
    background: #007bff;
    color: white;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s;
  }
  
  label:hover {
    background: #0056b3;
  }
  
  label.uploading {
    background: #6c757d;
    cursor: not-allowed;
  }
  
  .help-text {
    font-size: 14px;
    color: #666;
    margin: 8px 0 0 0;
  }
</style>
```

### API 集成与 Toast

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  let users = [];
  let loading = false;
  let newUser = { name: '', email: '' };
  
  async function fetchUsers() {
    loading = true;
    window.Toast.show("加载用户中...", "info", 2);
    
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1500));
      
      // 模拟数据
      users = [
        { id: 1, name: '张伟', email: 'zhangwei@example.com' },
        { id: 2, name: '李娜', email: 'lina@example.com' }
      ];
      
      window.Toast.show(`成功加载 ${users.length} 个用户`, "success", 3);
      
    } catch (error) {
      window.Toast.show("加载用户失败", "error", 4);
    } finally {
      loading = false;
    }
  }
  
  async function addUser() {
    if (!newUser.name || !newUser.email) {
      window.Toast.show("请填写所有字段", "error", 3);
      return;
    }
    
    loading = true;
    
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      const user = {
        id: Date.now(),
        name: newUser.name,
        email: newUser.email
      };
      
      users = [...users, user];
      newUser = { name: '', email: '' };
      
      window.Toast.show("用户添加成功！", "success", 3);
      
    } catch (error) {
      window.Toast.show("添加用户失败", "error", 4);
    } finally {
      loading = false;
    }
  }
  
  async function deleteUser(userId) {
    if (!confirm('您确定要删除此用户吗？')) {
      return;
    }
    
    try {
      // 模拟 API 调用
      await new Promise(resolve => setTimeout(resolve, 800));
      
      users = users.filter(u => u.id !== userId);
      window.Toast.show("用户删除成功", "success", 3);
      
    } catch (error) {
      window.Toast.show("删除用户失败", "error", 4);
    }
  }
</script>

<ToastBoard />

<div class="app">
  <h3>用户管理</h3>
  
  <div class="actions">
    <button on:click={fetchUsers} disabled={loading}>
      {loading ? '加载中...' : '加载用户'}
    </button>
  </div>
  
  <div class="add-user">
    <h4>添加新用户</h4>
    <input 
      bind:value={newUser.name} 
      placeholder="姓名" 
      disabled={loading}
    />
    <input 
      bind:value={newUser.email} 
      placeholder="电子邮件" 
      disabled={loading}
    />
    <button on:click={addUser} disabled={loading}>
      {loading ? '添加中...' : '添加用户'}
    </button>
  </div>
  
  <div class="users">
    <h4>用户 ({users.length})</h4>
    {#each users as user}
      <div class="user-item">
        <div>
          <strong>{user.name}</strong>
          <span>{user.email}</span>
        </div>
        <button on:click={() => deleteUser(user.id)}>删除</button>
      </div>
    {/each}
  </div>
</div>

<style>
  .app {
    max-width: 600px;
    margin: 20px;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .actions {
    margin-bottom: 20px;
  }
  
  .add-user {
    margin-bottom: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .add-user h4 {
    margin: 0 0 12px 0;
  }
  
  .add-user input {
    margin: 0 8px 8px 0;
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  
  .users h4 {
    margin-bottom: 12px;
  }
  
  .user-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px;
    border: 1px solid #eee;
    border-radius: 4px;
    margin-bottom: 8px;
  }
  
  .user-item div {
    display: flex;
    flex-direction: column;
  }
  
  .user-item span {
    font-size: 14px;
    color: #666;
  }
  
  button {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    background: #007bff;
    color: white;
  }
  
  button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }
</style>
```

### 底部定位的 Toast

```svelte
<script>
  import ToastBoard from "@ticatec/uniface-element/ToastBoard";
  
  function showBottomToast() {
    window.Toast.show("此 Toast 从底部显示！", "info", 4);
  }
</script>

<!-- 将 Toast 定位在屏幕底部 -->
<ToastBoard fromBottom={true} />

<button on:click={showBottomToast}>显示底部 Toast</button>
```

## 功能

- **多种类型**：成功、错误和信息类型，带有不同的颜色
- **自动消失**：可配置的持续时间，自动隐藏
- **流畅动画**：从顶部或底部飞入的过渡效果
- **全局访问**：通过 `window.Toast.show()` 从任何地方调用
- **定位**：支持屏幕顶部或底部定位
- **堆叠**：支持多个 Toast 按顺序显示
- **响应式**：适用于所有屏幕尺寸

## 样式

Toast 组件使用 CSS 自定义属性进行主题化：

```css
:root {
  --uniface-toast-text-color: #ffffff;
  --uniface-toast-error-background-color: #FF685B;
  --uniface-toast-info-background-color: #0061FF;
  --uniface-toast-successful-background-color: #2DC071;
}
```

### 自定义样式

```css
/* 覆盖 Toast 颜色 */
:root {
  --uniface-toast-error-background-color: #dc3545;
  --uniface-toast-info-background-color: #17a2b8;
  --uniface-toast-successful-background-color: #28a745;
}

/* 自定义 Toast 面板样式 */
.uniface-toast-board {
  /* 你的自定义样式 */
}
```

## 最佳实践

1. **全局设置**：在根布局/应用组件中仅包含一次 ToastBoard
2. **消息清晰**：使用用户能快速理解的简洁消息
3. **持续时间**：错误消息使用较长的持续时间，成功消息使用较短的时间
4. **类型一致性**：在整个应用中保持一致的 Toast 类型
5. **定位**：根据你的 UI 布局选择顶部或底部定位
6. **错误处理**：为异步操作始终显示 Toast 反馈

## 可访问性

- 高对比度颜色以提高可读性
- 自动消失防止屏幕杂乱
- 非侵入式定位
- 具有不同颜色的清晰视觉层次

## 浏览器支持

- 支持 CSS 自定义属性的现代浏览器
- 兼容 Svelte 5+
- 支持流畅动画的浏览器
- 为旧浏览器提供优雅的降级支持

## 相关组件

- `MessageBox` - 用于重要消息的模态对话框
- `AlertBar` - 持久化的警告通知
- `NotificationCenter` - 高级通知管理