---
nav:
  title: 组件
  order: 1
group:
  title: Data Display
  order: 1
title: MultilevelCard
order: 2
category: pro
---

# MultilevelCard 多层级卡片

MultilevelCard 是一个多层级卡片组件，支持点击或悬停展开次级面板，专为分类导航、多级菜单等场景设计。

## 何时使用

- 需要展示多层级的分类导航
- 作为下拉式菜单或弹出式面板的触发器
- 需要在主卡片下展示详细内容或子选项
- 商品分类、功能菜单等多级交互场景
- 需要支持点击或悬停两种触发方式

## 基础使用

### 基础多层级卡片（点击触发）

最简单的用法，点击主卡片展开次级面板。

```tsx
import { PisellCards } from '@pisell/materials';
import { useState } from 'react';

export default () => {
  const [active, setActive] = useState(false);

  return (
    <PisellCards.MultilevelCard
      card={(props) => (
        <PisellCards.TextCard
          text="分类 1"
          active={{ value: props.active }}
          onClick={() => setActive(!active)}
        />
      )}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          <div>子分类 1</div>
          <div>子分类 2</div>
          <div>子分类 3</div>
        </div>
      )}
      active={active}
      trigger="click"
    />
  );
};
```

### 悬停触发

鼠标悬停时展开次级面板，离开时收起。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="分类 1" />}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          <div>子分类 1</div>
          <div>子分类 2</div>
          <div>子分类 3</div>
        </div>
      )}
      trigger="hover"
    />
  );
};
```

## 触发方式配置

### 点击触发（click）

点击主卡片切换展开/收起状态。

```tsx
import { PisellCards } from '@pisell/materials';
import { useState } from 'react';

export default () => {
  const [active, setActive] = useState(false);

  return (
    <PisellCards.MultilevelCard
      card={(props) => (
        <PisellCards.GraphicTextCard
          text="点击展开"
          variant="horizontal"
          active={{ value: props.active }}
          onClick={() => setActive(!active)}
        />
      )}
      panel={(props) => (
        <div
          style={{
            padding: '16px',
            background: '#fff',
            border: '1px solid #e8e8e8',
          }}
        >
          点击模式：再次点击收起
        </div>
      )}
      active={active}
      trigger="click"
    />
  );
};
```

### 悬停触发（hover）

鼠标悬停展开，离开后延迟收起。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => (
        <PisellCards.GraphicTextCard text="悬停展开" variant="horizontal" />
      )}
      panel={(props) => (
        <div
          style={{
            padding: '16px',
            background: '#fff',
            border: '1px solid #e8e8e8',
          }}
        >
          悬停模式：鼠标离开后自动收起
        </div>
      )}
      trigger="hover"
    />
  );
};
```

### 悬停延迟配置

配置鼠标进入和离开的延迟时间，避免频繁触发。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => (
        <PisellCards.GraphicTextCard text="自定义延迟" variant="horizontal" />
      )}
      panel={(props) => (
        <div
          style={{
            padding: '16px',
            background: '#fff',
            border: '1px solid #e8e8e8',
          }}
        >
          进入延迟 300ms，离开延迟 500ms
        </div>
      )}
      trigger="hover"
      hoverDelay={{
        enter: 300,
        leave: 500,
      }}
    />
  );
};
```

## 次级面板配置

### 面板宽度模式

#### 自动宽度（auto）

内容撑满，宽度根据内容自适应。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="自动宽度" />}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          内容自适应宽度
        </div>
      )}
      trigger="hover"
      panelWidthMode="auto"
    />
  );
};
```

#### 固定宽度（fixed）

指定固定宽度值。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="固定宽度" />}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          固定 300px 宽度
        </div>
      )}
      trigger="hover"
      panelWidthMode="fixed"
      panelWidth={300}
    />
  );
};
```

#### 撑满容器（full）

撑满父容器宽度（默认）。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <div style={{ width: '100%', border: '1px dashed #ccc', padding: 16 }}>
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="撑满容器" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            撑满父容器宽度
          </div>
        )}
        trigger="hover"
        panelWidthMode="full"
      />
    </div>
  );
};
```

### 面板样式配置

自定义次级面板的样式和类名。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="自定义样式" />}
      panel={(props) => <div style={{ padding: '16px' }}>自定义面板样式</div>}
      trigger="hover"
      panelStyle={{
        background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
        color: '#fff',
        borderRadius: 8,
        boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
      }}
      panelClassName="custom-panel"
    />
  );
};
```

### 面板容器（Portal）

将次级面板渲染到指定的 DOM 节点中。

```tsx
import { PisellCards } from '@pisell/materials';
import { useRef } from 'react';

export default () => {
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <>
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="Portal 渲染" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            我被渲染到指定容器中了
          </div>
        )}
        trigger="hover"
        panelContainer={() => containerRef.current}
      />
      <div
        ref={containerRef}
        style={{
          marginTop: 20,
          padding: 16,
          border: '2px dashed #1890ff',
        }}
      />
    </>
  );
};
```

## 动画配置

### 启用/禁用动画

控制次级面板展开/收起时的过渡动画。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <div style={{ display: 'flex', gap: '16px' }}>
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="有动画" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            展开有动画效果
          </div>
        )}
        trigger="hover"
        animation={true}
      />
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="无动画" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            展开无动画效果
          </div>
        )}
        trigger="hover"
        animation={false}
      />
    </div>
  );
};
```

### 动画持续时间

自定义动画持续时间（毫秒）。

```tsx
import { PisellCards } from '@pisell/materials';

export default () => {
  return (
    <div style={{ display: 'flex', gap: '16px' }}>
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="快速动画" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            150ms 动画
          </div>
        )}
        trigger="hover"
        animation={true}
        animationDuration={150}
      />
      <PisellCards.MultilevelCard
        card={(props) => <PisellCards.TextCard text="慢速动画" />}
        panel={(props) => (
          <div style={{ padding: '16px', background: '#f5f5f5' }}>
            600ms 动画
          </div>
        )}
        trigger="hover"
        animation={true}
        animationDuration={600}
      />
    </div>
  );
};
```

## 事件回调

### 展开状态变化回调

监听次级面板的展开/收起状态变化。

```tsx
import { PisellCards } from '@pisell/materials';
import { message } from 'antd';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="监听状态变化" />}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          次级面板内容
        </div>
      )}
      trigger="hover"
      onExpandChange={(expanded) => {
        message.info(expanded ? '面板已展开' : '面板已收起');
      }}
    />
  );
};
```

### 面板挂载/卸载回调

监听次级面板的挂载和卸载事件。

```tsx
import { PisellCards } from '@pisell/materials';
import { message } from 'antd';

export default () => {
  return (
    <PisellCards.MultilevelCard
      card={(props) => <PisellCards.TextCard text="监听挂载/卸载" />}
      panel={(props) => (
        <div style={{ padding: '16px', background: '#f5f5f5' }}>
          次级面板内容
        </div>
      )}
      trigger="hover"
      onPanelMount={() => {
        console.log('面板已挂载');
        message.success('面板已挂载');
      }}
      onPanelUnmount={() => {
        console.log('面板已卸载');
        message.info('面板已卸载');
      }}
    />
  );
};
```

## 综合示例

### 多级分类导航

```tsx
import { PisellCards } from '@pisell/materials';
import { useState } from 'react';

export default () => {
  const [activeCategory, setActiveCategory] = useState<string | null>(null);

  const categories = [
    {
      id: '1',
      name: '电子产品',
      children: ['手机', '电脑', '相机', '耳机'],
    },
    {
      id: '2',
      name: '服装鞋包',
      children: ['男装', '女装', '童装', '运动鞋'],
    },
    {
      id: '3',
      name: '美妆护肤',
      children: ['面部护理', '彩妆', '香水', '美容工具'],
    },
  ];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
      {categories.map((category) => (
        <PisellCards.MultilevelCard
          key={category.id}
          card={(props) => (
            <PisellCards.GraphicTextCard
              text={category.name}
              variant="horizontal"
              active={{ value: props.active }}
              onClick={() =>
                setActiveCategory(
                  activeCategory === category.id ? null : category.id
                )
              }
            />
          )}
          panel={(props) => (
            <div
              style={{
                padding: '12px 16px',
                background: '#fafafa',
                borderTop: '1px solid #e8e8e8',
              }}
            >
              {category.children.map((child, index) => (
                <div
                  key={index}
                  style={{
                    padding: '8px 0',
                    cursor: 'pointer',
                    color: '#666',
                  }}
                  onClick={() => console.log('点击子分类:', child)}
                >
                  {child}
                </div>
              ))}
            </div>
          )}
          active={activeCategory === category.id}
          trigger="click"
          panelWidthMode="full"
        />
      ))}
    </div>
  );
};
```

### 悬停式菜单

```tsx
import { PisellCards } from '@pisell/materials';
import {
  AppstoreOutlined,
  ShoppingOutlined,
  UserOutlined,
} from '@ant-design/icons';

export default () => {
  const menuItems = [
    {
      id: '1',
      name: '商品管理',
      icon: <AppstoreOutlined />,
      children: ['商品列表', '添加商品', '分类管理', '库存管理'],
    },
    {
      id: '2',
      name: '订单管理',
      icon: <ShoppingOutlined />,
      children: ['订单列表', '待发货', '退款管理', '售后服务'],
    },
    {
      id: '3',
      name: '用户管理',
      icon: <UserOutlined />,
      children: ['用户列表', '会员管理', '权限设置', '黑名单'],
    },
  ];

  return (
    <div style={{ display: 'flex', gap: '16px' }}>
      {menuItems.map((item) => (
        <PisellCards.MultilevelCard
          key={item.id}
          card={(props) => (
            <PisellCards.GraphicTextCard
              text={item.name}
              icon={item.icon}
              variant="vertical-icon"
            />
          )}
          panel={(props) => (
            <div
              style={{
                padding: '12px',
                background: '#fff',
                border: '1px solid #e8e8e8',
                borderRadius: 8,
                boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
                minWidth: 120,
              }}
            >
              {item.children.map((child, index) => (
                <div
                  key={index}
                  style={{
                    padding: '8px 12px',
                    cursor: 'pointer',
                    borderRadius: 4,
                    transition: 'background 0.2s',
                  }}
                  onMouseEnter={(e) => {
                    e.currentTarget.style.background = '#f5f5f5';
                  }}
                  onMouseLeave={(e) => {
                    e.currentTarget.style.background = 'transparent';
                  }}
                  onClick={() => console.log('点击菜单:', child)}
                >
                  {child}
                </div>
              ))}
            </div>
          )}
          trigger="hover"
          hoverDelay={{ enter: 100, leave: 200 }}
          panelWidthMode="auto"
          animation={true}
          animationDuration={200}
        />
      ))}
    </div>
  );
};
```

### 嵌套多层级卡片

```tsx
import { PisellCards } from '@pisell/materials';
import { useState } from 'react';

export default () => {
  const [level1Active, setLevel1Active] = useState(false);
  const [level2Active, setLevel2Active] = useState(false);

  return (
    <PisellCards.MultilevelCard
      card={(props) => (
        <PisellCards.TextCard
          text="一级分类"
          active={{ value: props.active }}
          onClick={() => setLevel1Active(!level1Active)}
        />
      )}
      panel={(props) => (
        <div style={{ padding: '8px' }}>
          <PisellCards.MultilevelCard
            card={(innerProps) => (
              <PisellCards.TextCard
                text="二级分类"
                active={{ value: innerProps.active }}
                onClick={() => setLevel2Active(!level2Active)}
              />
            )}
            panel={(innerProps) => (
              <div style={{ padding: '16px', background: '#e6f7ff' }}>
                三级分类内容
              </div>
            )}
            active={level2Active}
            trigger="click"
          />
        </div>
      )}
      active={level1Active}
      trigger="click"
      panelWidthMode="full"
    />
  );
};
```

## API

### MultilevelCardProps

| 参数              | 说明                                        | 类型                          | 默认值    |
| ----------------- | ------------------------------------------- | ----------------------------- | --------- |
| card              | 主卡片插槽（必填）                          | `(props: T) => ReactNode`     | -         |
| panel             | 次级面板插槽                                | `(props: T) => ReactNode`     | -         |
| trigger           | 触发方式                                    | `'click' \| 'hover'`          | `'click'` |
| hoverDelay        | hover 模式下的延迟时间配置                  | `MultilevelCardHoverDelay`    | -         |
| panelWidthMode    | 次级面板宽度模式                            | `'auto' \| 'fixed' \| 'full'` | `'full'`  |
| panelWidth        | 固定宽度值（panelWidthMode='fixed' 时有效） | `number \| string`            | -         |
| panelStyle        | 次级面板样式                                | `CSSProperties`               | -         |
| panelClassName    | 次级面板类名                                | `string`                      | -         |
| panelContainer    | 次级面板容器（使用 Portal 渲染）            | `() => HTMLElement \| null`   | -         |
| animation         | 是否启用动画                                | `boolean`                     | `true`    |
| animationDuration | 动画持续时间（毫秒）                        | `number`                      | `300`     |
| active            | 激活状态（click 模式下控制展开/收起）       | `boolean`                     | -         |
| disabled          | 禁用状态                                    | `boolean \| StateConfig`      | -         |
| loading           | 加载状态                                    | `boolean \| StateConfig`      | -         |
| onExpandChange    | 展开状态变化回调                            | `(expanded: boolean) => void` | -         |
| onPanelMount      | 次级面板渲染完成回调                        | `() => void`                  | -         |
| onPanelUnmount    | 次级面板卸载回调                            | `() => void`                  | -         |
| onClick           | 点击事件（click 模式下触发）                | `(event: MouseEvent) => void` | -         |
| className         | 自定义样式类名                              | `string`                      | -         |
| style             | 自定义样式                                  | `CSSProperties`               | -         |

### MultilevelCardHoverDelay

| 参数  | 说明                     | 类型     | 默认值 |
| ----- | ------------------------ | -------- | ------ |
| enter | 鼠标进入延迟时间（毫秒） | `number` | `100`  |
| leave | 鼠标离开延迟时间（毫秒） | `number` | `200`  |

## 注意事项

1. **触发模式**：

   - click 模式：需要手动控制 `active` 状态，通过主卡片的 onClick 事件切换
   - hover 模式：自动管理展开/收起状态，无需手动控制

2. **主卡片要求**：

   - click 模式下，主卡片需要支持 `active` 状态（如使用 TextCard、GraphicTextCard 等实现了 PisellBasicCardProps 的卡片）
   - hover 模式下，主卡片无特殊要求

3. **面板宽度**：

   - auto 模式：根据内容自适应宽度
   - fixed 模式：使用 panelWidth 指定固定宽度
   - full 模式：撑满主卡片宽度（默认）

4. **动画性能**：

   - 动画默认启用，持续时间为 300ms
   - 如需更流畅的体验，建议设置较短的动画时间（150-200ms）
   - 大量嵌套时可考虑禁用动画以提升性能

5. **Portal 渲染**：

   - 使用 `panelContainer` 可将面板渲染到指定容器
   - 适用于需要突破父容器限制的场景（如 overflow: hidden）

6. **事件冒泡**：

   - 注意主卡片和次级面板的点击事件可能会冒泡
   - 必要时使用 `e.stopPropagation()` 阻止事件冒泡

7. **嵌套使用**：

   - 支持嵌套使用，可实现多层级导航
   - 嵌套时注意管理各层级的展开状态

8. **无障碍访问**：
   - 组件已添加基础的键盘交互支持
   - 建议为主卡片添加适当的 ARIA 属性以提升可访问性
