# Tabs 标签页组件

一个灵活的标签页导航组件，支持水平滚动、可关闭标签页和自定义渲染。功能包括自动溢出处理、平滑滚动动画和标签页刷新功能。非常适合文档界面、仪表板和多面板应用。

## 安装

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

## 导入

```typescript
import Tabs, { type TabActionHandler, type TabCloseHandler, type TabRender } from "@ticatec/uniface-element/Tabs";
```

## 基本用法

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  let tabs = [
    { text: "首页" },
    { text: "产品" },
    { text: "服务" },
    { text: "关于" },
    { text: "联系我们" }
  ];
  
  let activeTab = tabs[0];
</script>

<Tabs {tabs} bind:activeTab>
  <div class="tab-content">
    <h2>{activeTab.text}</h2>
    <p>{activeTab.text} 标签页的内容</p>
  </div>
</Tabs>
```

## 属性

| 属性 | 类型 | 默认值 | 描述 |
|------|------|---------|-------------|
| `tabs` | `Array<any>` | `[]` | 标签页对象数组 |
| `activeTab` | `any` | `null` | 当前激活的标签页对象 |
| `textField` | `string` | `"text"` | 标签页显示文本的字段名称 |
| `simple` | `boolean` | `false` | 是否使用简约样式 |
| `closable` | `boolean \| TabActionHandler` | `false` | 标签页是否可关闭 |
| `scrollStep` | `number` | `100` | 滚动步长（像素） |
| `tabRender` | `TabRender` | `null` | 自定义标签页渲染组件 |
| `reloadHandler` | `TabActionHandler` | `null` | 标签页刷新动作的处理函数 |
| `closeHandler` | `TabCloseHandler` | `null` | 标签页关闭动作的处理函数 |
| `style` | `string` | `""` | 附加 CSS 样式 |
| `class` | `string` | `""` | CSS 类名 |

## 类型定义

### TabActionHandler
```typescript
type TabActionHandler = (tab: any) => void;
```

### TabCloseHandler
```typescript
type TabCloseHandler = (tab: any) => Promise<boolean>;
```

### TabRender
```typescript
type TabRender = (tab: any) => any;
```

## 示例

### 基本标签页导航

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  let navigationTabs = [
    { id: 1, text: "仪表板", icon: "dashboard" },
    { id: 2, text: "分析", icon: "analytics" },
    { id: 3, text: "报告", icon: "reports" },
    { id: 4, text: "设置", icon: "settings" }
  ];
  
  let currentTab = navigationTabs[0];
</script>

<div class="app-container">
  <Tabs tabs={navigationTabs} bind:activeTab={currentTab} style="height: 400px;">
    {#if currentTab.id === 1}
      <div class="dashboard">
        <h2>📊 仪表板</h2>
        <p>欢迎体验您的仪表板概览</p>
        <div class="stats">
          <div class="stat">用户数: 1,234</div>
          <div class="stat">收入: ¥12,345</div>
          <div class="stat">订单数: 567</div>
        </div>
      </div>
    {:else if currentTab.id === 2}
      <div class="analytics">
        <h2>📈 分析</h2>
        <p>查看您的性能指标</p>
        <div class="charts">
          <div class="chart">月增长: +15%</div>
          <div class="chart">转化率: 3.2%</div>
        </div>
      </div>
    {:else if currentTab.id === 3}
      <div class="reports">
        <h2>📋 报告</h2>
        <p>生成和查看报告</p>
        <button>生成月度报告</button>
        <button>导出数据</button>
      </div>
    {:else if currentTab.id === 4}
      <div class="settings">
        <h2>⚙️ 设置</h2>
        <p>配置您的偏好设置</p>
        <label><input type="checkbox"> 电子邮件通知</label>
        <label><input type="checkbox"> 深色模式</label>
      </div>
    {/if}
  </Tabs>
</div>

<style>
  .app-container {
    max-width: 800px;
    margin: 20px auto;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .dashboard, .analytics, .reports, .settings {
    padding: 20px;
  }
  
  .stats, .charts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 16px;
    margin-top: 16px;
  }
  
  .stat, .chart {
    padding: 12px;
    background: #f8f9fa;
    border-radius: 4px;
    text-align: center;
  }
  
  .reports button {
    margin: 8px 8px 8px 0;
    padding: 8px 16px;
    border: none;
    background: #007bff;
    color: white;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .settings label {
    display: block;
    margin: 8px 0;
  }
</style>
```

### 简约标签页

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  let documentTabs = [
    { text: "简介" },
    { text: "入门指南" },
    { text: "API 参考" },
    { text: "示例" }
  ];
  
  let currentDoc = documentTabs[0];
</script>

<div class="docs-container">
  <Tabs tabs={documentTabs} bind:activeTab={currentDoc} simple style="height: 300px;">
    <div class="doc-content">
      {#if currentDoc.text === "简介"}
        <h3>简介</h3>
        <p>欢迎体验我们的全面文档。本指南将帮助您快速上手我们的平台并充分利用其功能。</p>
      {:else if currentDoc.text === "入门指南"}
        <h3>入门指南</h3>
        <ol>
          <li>创建账户</li>
          <li>验证您的电子邮件</li>
          <li>完善您的个人资料</li>
          <li>开始使用平台</li>
        </ol>
      {:else if currentDoc.text === "API 参考"}
        <h3>API 参考</h3>
        <p>完整的 API 文档，包括端点、参数和示例。</p>
        <code>GET /api/users</code>
      {:else if currentDoc.text === "示例"}
        <h3>示例</h3>
        <p>实用示例和代码片段，帮助您实现常见用例。</p>
      {/if}
    </div>
  </Tabs>
</div>

<style>
  .docs-container {
    max-width: 600px;
    margin: 20px auto;
  }
  
  .doc-content {
    padding: 20px;
    background: #f8f9fa;
  }
  
  .doc-content h3 {
    margin-top: 0;
    color: #333;
  }
  
  .doc-content code {
    background: #e9ecef;
    padding: 4px 8px;
    border-radius: 4px;
    font-family: monospace;
  }
  
  .doc-content ol {
    padding-left: 20px;
  }
  
  .doc-content li {
    margin: 8px 0;
  }
</style>
```

### 可关闭标签页

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  let browserTabs = [
    { id: 1, text: "百度", url: "https://www.baidu.com", modified: false },
    { id: 2, text: "GitHub", url: "https://github.com", modified: true },
    { id: 3, text: "知乎", url: "https://www.zhihu.com", modified: false },
    { id: 4, text: "MDN 文档", url: "https://developer.mozilla.org", modified: false }
  ];
  
  let activeTab = browserTabs[0];
  
  async function handleTabClose(tab) {
    if (tab.modified) {
      const confirmed = confirm(`"${tab.text}" 有未保存的更改。是否仍要关闭？`);
      if (!confirmed) return false;
    }
    
    console.log(`关闭标签页：${tab.text}`);
    browserTabs = browserTabs.filter(t => t.id !== tab.id);
    return true;
  }
  
  function addNewTab() {
    const newId = Math.max(...browserTabs.map(t => t.id)) + 1;
    const newTab = {
      id: newId,
      text: "新标签页",
      url: "about:blank",
      modified: false
    };
    
    browserTabs = [...browserTabs, newTab];
    activeTab = newTab;
  }
</script>

<div class="browser-container">
  <div class="browser-header">
    <Tabs 
      tabs={browserTabs} 
      bind:activeTab 
      closable={true}
      closeHandler={handleTabClose}
      style="height: 350px;"
    >
      <div class="page-content">
        <div class="address-bar">
          <span>🔒 {activeTab.url}</span>
        </div>
        <div class="page-body">
          <h2>{activeTab.text}</h2>
          <p>这是 {activeTab.text} 的内容</p>
          {#if activeTab.modified}
            <p class="modified">⚠️ 此页面有未保存的更改</p>
          {/if}
          <button on:click={() => activeTab.modified = !activeTab.modified}>
            {activeTab.modified ? '标记为已保存' : '进行更改'}
          </button>
        </div>
      </div>
    </Tabs>
    <button class="new-tab-btn" on:click={addNewTab}>+</button>
  </div>
</div>

<style>
  .browser-container {
    max-width: 900px;
    margin: 20px auto;
    border: 1px solid #ddd;
    border-radius: 8px;
    overflow: hidden;
  }
  
  .browser-header {
    display: flex;
    align-items: stretch;
  }
  
  .new-tab-btn {
    width: 40px;
    border: none;
    background: #f8f9fa;
    cursor: pointer;
    font-size: 18px;
    border-left: 1px solid #ddd;
  }
  
  .new-tab-btn:hover {
    background: #e9ecef;
  }
  
  .page-content {
    padding: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
  }
  
  .address-bar {
    padding: 8px 16px;
    background: #f8f9fa;
    border-bottom: 1px solid #ddd;
    font-family: monospace;
    font-size: 14px;
  }
  
  .page-body {
    padding: 20px;
    flex: 1;
  }
  
  .modified {
    color: #dc3545;
    font-weight: 500;
  }
  
  button {
    margin-top: 12px;
    padding: 8px 16px;
    border: 1px solid #007bff;
    background: white;
    color: #007bff;
    border-radius: 4px;
    cursor: pointer;
  }
  
  button:hover {
    background: #007bff;
    color: white;
  }
</style>
```

### 带刷新功能的标签页

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  let dataTabs = [
    { id: 1, text: "销售数据", lastUpdated: "2 分钟前", loading: false },
    { id: 2, text: "用户分析", lastUpdated: "5 分钟前", loading: false },
    { id: 3, text: "性能", lastUpdated: "10 分钟前", loading: false }
  ];
  
  let activeDataTab = dataTabs[0];
  
  function handleTabReload(tab) {
    console.log(`刷新标签页：${tab.text}`);
    
    // 设置加载状态
    tab.loading = true;
    dataTabs = [...dataTabs];
    
    // 模拟数据刷新
    setTimeout(() => {
      tab.loading = false;
      tab.lastUpdated = "刚刚更新";
      dataTabs = [...dataTabs];
    }, 2000);
  }
</script>

<div class="data-dashboard">
  <Tabs 
    tabs={dataTabs} 
    bind:activeTab={activeDataTab} 
    reloadHandler={handleTabReload}
    style="height: 400px;"
  >
    <div class="data-content">
      <div class="data-header">
        <h3>{activeDataTab.text}</h3>
        <div class="last-updated">
          {#if activeDataTab.loading}
            <span class="loading">🔄 正在刷新...</span>
          {:else}
            <span>最后更新：{activeDataTab.lastUpdated}</span>
          {/if}
        </div>
      </div>
      
      <div class="data-body">
        {#if activeDataTab.id === 1}
          <div class="chart-placeholder">
            <h4>销售概览</h4>
            <div class="metrics">
              <div>总销售额：¥45,678</div>
              <div>增长率：+12.5%</div>
              <div>交易数：234</div>
            </div>
          </div>
        {:else if activeDataTab.id === 2}
          <div class="chart-placeholder">
            <h4>用户参与度</h4>
            <div class="metrics">
              <div>活跃用户：1,890</div>
              <div>会话时长：4:32</div>
              <div>跳出率：23%</div>
            </div>
          </div>
        {:else if activeDataTab.id === 3}
          <div class="chart-placeholder">
            <h4>系统性能</h4>
            <div class="metrics">
              <div>响应时间：145ms</div>
              <div>正常运行时间：99.9%</div>
              <div>CPU 使用率：45%</div>
            </div>
          </div>
        {/if}
      </div>
    </div>
  </Tabs>
</div>

<style>
  .data-dashboard {
    max-width: 800px;
    margin: 20px auto;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .data-content {
    padding: 20px;
    height: 100%;
    display: flex;
    flex-direction: column;
  }
  
  .data-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 12px;
    border-bottom: 1px solid #eee;
  }
  
  .data-header h3 {
    margin: 0;
  }
  
  .last-updated {
    font-size: 14px;
    color: #666;
  }
  
  .loading {
    color: #007bff;
    animation: pulse 1.5s infinite;
  }
  
  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }
  }
  
  .chart-placeholder {
    flex: 1;
    background: #f8f9fa;
    border-radius: 8px;
    padding: 20px;
    text-align: center;
  }
  
  .chart-placeholder h4 {
    margin: 0 0 20px 0;
    color: #333;
  }
  
  .metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 16px;
  }
  
  .metrics div {
    padding: 16px;
    background: white;
    border-radius: 4px;
    font-weight: 500;
  }
</style>
```

### 自定义标签页渲染

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  import CustomTab from "./CustomTab.svelte";
  
  let projectTabs = [
    { 
      id: 1, 
      text: "前端", 
      status: "active", 
      notifications: 3,
      color: "#007bff"
    },
    { 
      id: 2, 
      text: "后端", 
      status: "warning", 
      notifications: 1,
      color: "#ffc107"
    },
    { 
      id: 3, 
      text: "数据库", 
      status: "error", 
      notifications: 5,
      color: "#dc3545"
    },
    { 
      id: 4, 
      text: "运维", 
      status: "success", 
      notifications: 0,
      color: "#28a745"
    }
  ];
  
  let activeProject = projectTabs[0];
  
  async function handleProjectClose(tab) {
    const confirmed = confirm(`关闭项目 "${tab.text}"？`);
    return confirmed;
  }
</script>

<!-- CustomTab.svelte -->
<script>
  export let tab;
  export let closeTab = null;
  export let reloadTab = null;
  export let closable = false;
  
  function getStatusIcon(status) {
    switch (status) {
      case 'active': return '🟢';
      case 'warning': return '🟡';
      case 'error': return '🔴';
      case 'success': return '✅';
      default: return '⚪';
    }
  }
</script>

<div class="custom-tab" style="border-left: 3px solid {tab.color};">
  <div class="tab-content">
    <span class="status-icon">{getStatusIcon(tab.status)}</span>
    <span class="tab-text">{tab.text}</span>
    {#if tab.notifications > 0}
      <span class="notification-badge">{tab.notifications}</span>
    {/if}
  </div>
  
  <div class="tab-actions">
    {#if reloadTab}
      <button class="action-btn" on:click={reloadTab}>🔄</button>
    {/if}
    {#if closable && closeTab}
      <button class="action-btn close" on:click={closeTab}>×</button>
    {/if}
  </div>
</div>

<style>
  .custom-tab {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 12px;
    background: white;
    border-radius: 4px 4px 0 0;
    min-width: 120px;
  }
  
  .tab-content {
    display: flex;
    align-items: center;
    gap: 6px;
  }
  
  .status-icon {
    font-size: 12px;
  }
  
  .tab-text {
    font-weight: 500;
  }
  
  .notification-badge {
    background: #dc3545;
    color: white;
    font-size: 10px;
    padding: 2px 6px;
    border-radius: 10px;
    min-width: 16px;
    text-align: center;
  }
  
  .tab-actions {
    display: flex;
    gap: 4px;
  }
  
  .action-btn {
    background: none;
    border: none;
    padding: 2px 4px;
    cursor: pointer;
    border-radius: 2px;
    font-size: 12px;
  }
  
  .action-btn:hover {
    background: #f8f9fa;
  }
  
  .action-btn.close:hover {
    background: #dc3545;
    color: white;
  }
</style>

<!-- 主组件 -->
<div class="project-dashboard">
  <Tabs 
    tabs={projectTabs} 
    bind:activeTab={activeProject}
    tabRender={CustomTab}
    closable={true}
    closeHandler={handleProjectClose}
    style="height: 350px;"
  >
    <div class="project-content">
      <h3>{activeProject.text} 项目</h3>
      <div class="project-status">
        <span>状态：</span>
        <span class="status {activeProject.status}">{activeProject.status.toUpperCase()}</span>
      </div>
      
      {#if activeProject.notifications > 0}
        <div class="notifications">
          <h4>通知 ({activeProject.notifications})</h4>
          <ul>
            {#each Array(activeProject.notifications) as _, i}
              <li>{activeProject.text} 的通知 #{i + 1}</li>
            {/each}
          </ul>
        </div>
      {:else}
        <p>此项目没有通知。</p>
      {/if}
    </div>
  </Tabs>
</div>

<style>
  .project-dashboard {
    max-width: 800px;
    margin: 20px auto;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .project-content {
    padding: 20px;
  }
  
  .project-status {
    margin: 16px 0;
  }
  
  .status {
    padding: 4px 8px;
    border-radius: 4px;
    font-weight: bold;
    text-transform: uppercase;
    font-size: 12px;
  }
  
  .status.active {
    background: #cce5ff;
    color: #004085;
  }
  
  .status.warning {
    background: #fff3cd;
    color: #856404;
  }
  
  .status.error {
    background: #f8d7da;
    color: #721c24;
  }
  
  .status.success {
    background: #d4edda;
    color: #155724;
  }
  
  .notifications h4 {
    margin: 20px 0 12px 0;
  }
  
  .notifications ul {
    padding-left: 20px;
  }
  
  .notifications li {
    margin: 8px 0;
  }
</style>
```

### 水平滚动标签页

```svelte
<script>
  import Tabs from "@ticatec/uniface-element/Tabs";
  
  // 创建多个标签页以展示滚动效果
  let manyTabs = Array.from({length: 15}, (_, i) => ({
    id: i + 1,
    text: `标签页 ${i + 1}`,
    content: `这是标签页 ${i + 1} 的内容。Lorem ipsum dolor sit amet, consectetur adipiscing elit。`
  }));
  
  let currentTab = manyTabs[0];
</script>

<div class="scrollable-demo">
  <h3>水平滚动标签页演示</h3>
  <p>此示例展示了当标签页数量过多时如何自动滚动。</p>
  
  <Tabs 
    tabs={manyTabs} 
    bind:activeTab={currentTab}
    scrollStep={150}
    style="height: 300px; width: 600px;"
  >
    <div class="scroll-content">
      <h4>{currentTab.text}</h4>
      <p>{currentTab.content}</p>
      <div class="tab-info">
        <p><strong>标签页 ID：</strong> {currentTab.id}</p>
        <p><strong>位置：</strong> {manyTabs.indexOf(currentTab) + 1} / {manyTabs.length}</p>
      </div>
    </div>
  </Tabs>
</div>

<style>
  .scrollable-demo {
    max-width: 700px;
    margin: 20px auto;
    padding: 20px;
  }
  
  .scrollable-demo h3 {
    margin-bottom: 8px;
  }
  
  .scrollable-demo p {
    margin-bottom: 20px;
    color: #666;
  }
  
  .scroll-content {
    padding: 20px;
    background: #f8f9fa;
    height: 100%;
  }
  
  .tab-info {
    margin-top: 20px;
    padding: 12px;
    background: white;
    border-radius: 4px;
    border-left: 3px solid #007bff;
  }
  
  .tab-info p {
    margin: 4px 0;
    font-size: 14px;
  }
</style>
```

## 功能

- **水平滚动**：标签页溢出时自动显示滚动控件
- **可关闭标签页**：支持可选的标签页关闭功能，带有确认回调
- **刷新功能**：内置刷新按钮，支持自定义处理程序
- **自定义渲染**：使用自定义组件定义标签页外观
- **平滑动画**：CSS 过渡和运动补间
- **键盘可访问性**：支持完整的键盘导航
- **响应式设计**：适配容器宽度
- **简约模式**：简洁的样式选项

## 样式

Tabs 组件支持广泛的 CSS 自定义：

```css
/* 标签页面板容器 */
.uniface-tab-panel {
  /* 自定义样式 */
}

/* 单个标签页样式 */
.uniface-tab {
  /* 标签页外观 */
}

.uniface-tab.active {
  /* 激活标签页样式 */
}

/* 简约模式样式 */
.uniface-tabs-wrap.simple {
  /* 简约模式覆盖样式 */
}

/* 滚动按钮 */
.scroll-left, .scroll-right {
  /* 滚动控件样式 */
}
```

## 可访问性

- 支持 Tab 和箭头键的键盘导航
- 为屏幕阅读器提供 ARIA 属性
- 焦点管理和视觉指示器
- 语义化 HTML 结构
- 高对比度支持

## 最佳实践

1. **标签页管理**：保持合理的标签页数量以提升用户体验
2. **内容加载**：使用刷新功能加载动态内容
3. **关闭确认**：在关闭已修改的标签页前始终进行确认
4. **自定义渲染**：使用自定义标签页组件处理复杂布局
5. **响应式设计**：在不同屏幕尺寸上测试标签页行为
6. **性能**：考虑为标签页内容实现懒加载

## 浏览器支持

- 支持 CSS Grid 和 Flexbox 的现代浏览器
- 兼容 Svelte 5+
- 支持平滑动画的浏览器
- 触控友好界面
- 完整的 TypeScript 支持

## 相关组件

- `Accordion` - 可折叠内容面板
- `Stepper` - 逐步导航
- `Breadcrumb` - 层级导航
- `Sidebar` - 侧边导航组件