---
title: 开发文档
order: 3
---

# MultilevelCard 开发文档

## 1. 组件基本信息

### 1.1 组件名称

- 组件名称（中文）：多层级卡片
- 组件名称（英文）：MultilevelCard
- 组件编号：pcp_dd_multilevelCard_V1.0

### 1.2 组件版本

- 版本号：1.0.0
- 更新时间：2024-12

### 1.3 组件分类

- 组件类型：Pro Components
- 组件分类：Data Display
- 组件位置：packages/materials/src/components/PisellCards/components/MultilevelCard

## 2. 组件需求点罗列

### 2.1 核心功能

- [ ] 支持传入任意 Card 组件作为主卡片（通过插槽）
- [ ] 支持传入自定义内容作为次级面板（通过插槽）
- [ ] 支持根据主卡片的 active 状态控制次级面板的展开/收起
- [ ] 支持 click 和 hover 两种触发方式
- [ ] 支持展开/收起的过渡动画

### 2.2 布局与定位

- [ ] 次级面板默认在主卡片下方展示
- [ ] 支持自定义次级面板的容器（Portal 功能）
- [ ] 支持配置次级面板的宽度模式（auto/fixed/full）
- [ ] 支持自定义次级面板的样式

### 2.3 交互逻辑

- [ ] Click 模式：通过 active 属性控制展开/收起
- [ ] Hover 模式：鼠标进入展开，离开时收起
- [ ] 支持防抖延迟（hover 模式下）

### 2.4 扩展性

- [ ] 支持自定义次级面板的样式
- [ ] 支持自定义次级面板的类名
- [ ] 支持展开/收起的回调事件
- [ ] 支持动画配置

## 3. 组件描述

MultilevelCard 是一个多层级卡片组件，用于展示分层级数据。组件本身不包含具体的 UI 样式，而是通过插槽机制接收主卡片和次级面板内容，提供展开/收起的交互逻辑和动画效果。

### 3.1 设计思路

- **高度解耦**：主卡片和次级面板都通过插槽传入，组件只负责控制展开/收起逻辑
- **灵活定位**：支持默认定位和 Portal 定位两种方式
- **多种触发**：支持 click 和 hover 两种交互方式
- **平滑过渡**：内置动画效果，提升用户体验

### 3.2 适用场景

- 多级分类导航（如商品分类、内容分类）
- 级联选择器
- 树形菜单展示
- 多级筛选条件
- 组织架构展示

## 4. 组件 API 设计

### 4.1 MultilevelCardProps

```
interface MultilevelCardProps {
  // ========== 插槽配置 ==========
  /**
   * 主卡片插槽
   * 传入任意 Card 组件（如 TextCard、GraphicTextCard 等）
   */
  card: ReactNode;

  /**
   * 次级面板插槽
   * 传入自定义内容，当主卡片 active 时展示
   */
  panel?: ReactNode;

  // ========== 触发方式配置 ==========
  /**
   * 触发方式
   * - click: 点击触发
   * - hover: 悬停触发
   * @default 'click'
   */
  trigger?: 'click' | 'hover';

  /**
   * hover 模式下的延迟时间（毫秒）
   * @default { enter: 100, leave: 200 }
   */
  hoverDelay?: {
    enter?: number;
    leave?: number;
  };

  // ========== 次级面板配置 ==========
  /**
   * 次级面板宽度模式
   * - auto: 内容撑满
   * - fixed: 固定宽度
   * - full: 撑满父容器
   * @default 'full'
   */
  panelWidthMode?: 'auto' | 'fixed' | 'full';

  /**
   * 固定宽度值
   * 当 panelWidthMode='fixed' 时有效
   */
  panelWidth?: number | string;

  /**
   * 次级面板样式
   */
  panelStyle?: CSSProperties;

  /**
   * 次级面板类名
   */
  panelClassName?: string;

  /**
   * 次级面板容器
   * 传入 DOM 节点，次级面板将渲染到该节点中（Portal）
   * 不传则默认渲染在组件内部
   */
  panelContainer?: HTMLElement | null;

  // ========== 动画配置 ==========
  /**
   * 是否启用动画
   * @default true
   */
  animation?: boolean;

  /**
   * 动画持续时间（毫秒）
   * @default 300
   */
  animationDuration?: number;

  // ========== 事件回调 ==========
  /**
   * 展开状态变化回调
   */
  onExpandChange?: (expanded: boolean) => void;

  /**
   * 次级面板渲染完成回调
   */
  onPanelMount?: () => void;

  /**
   * 次级面板卸载回调
   */
  onPanelUnmount?: () => void;

  // ========== 其他配置 ==========
  /**
   * 组件类名
   */
  className?: string;

  /**
   * 组件样式
   */
  style?: CSSProperties;
}
```

## 5. 组件模块拆解

### 5.1 目录结构

```
MultilevelCard/
├── index.tsx                      # 组件导出
├── MultilevelCard.tsx             # 组件主体
├── MultilevelCard.less            # 组件样式
├── MultilevelCard.test.tsx        # 组件测试
├── types.ts                       # 类型定义
├── hooks/
│   ├── index.ts                   # Hooks 导出
│   ├── useMultilevelCardExpand.ts # 展开/收起逻辑 Hook
│   └── useMultilevelCardHover.ts  # Hover 交互 Hook
├── components/
│   ├── index.tsx                  # 子组件导出
│   ├── CardWrapper.tsx            # 主卡片包装器
│   └── PanelWrapper.tsx           # 次级面板包装器
├── utils/
│   ├── index.ts                   # 工具函数导出
│   └── getCardActive.ts           # 获取主卡片 active 状态
└── docs/
    ├── MultilevelCard.md          # 使用文档
    ├── MultilevelCard.$tab-design.md   # 设计文档
    ├── MultilevelCard.$tab-dev.md      # 开发文档
    └── MultilevelCard.$tab-test.md     # 测试文档
```

### 5.2 核心模块说明

#### 5.2.1 主组件（MultilevelCard.tsx）

- 接收 props 并管理组件状态
- 组合 CardWrapper 和 PanelWrapper
- 处理触发方式逻辑（click/hover）

#### 5.2.2 CardWrapper（子组件）

- 包装主卡片插槽
- 监听卡片的 active 状态变化
- 处理点击和 hover 事件

#### 5.2.3 PanelWrapper（子组件）

- 包装次级面板插槽
- 处理展开/收起动画
- 支持 Portal 渲染

#### 5.2.4 useMultilevelCardExpand（Hook）

- 管理展开/收起状态
- 监听主卡片的 active 状态
- 触发展开状态变化回调

#### 5.2.5 useMultilevelCardHover（Hook）

- 处理 hover 模式下的交互逻辑
- 支持延迟展开/收起
- 防抖处理

#### 5.2.6 getCardActive（工具函数）

- 从主卡片 props 中提取 active 状态
- 兼容不同类型的 Card 组件

## 6. 组件实现方案

### 6.1 技术选型

- **React Hooks**：使用 useState、useEffect、useRef 等管理状态和副作用
- **Portal**：使用 ReactDOM.createPortal 实现自定义容器渲染
- **CSS Transition**：使用 CSS transition 实现展开/收起动画
- **React.cloneElement**：克隆主卡片元素，注入事件监听

### 6.2 核心实现逻辑

#### 6.2.1 展开/收起状态管理

```
// 状态管理
const [expanded, setExpanded] = useState(false);

// 监听主卡片的 active 状态
useEffect(() => {
  const cardActive = getCardActive(card);
  if (trigger === 'click' && cardActive !== undefined) {
    setExpanded(cardActive);
  }
}, [card, trigger]);

// 触发回调
useEffect(() => {
  onExpandChange?.(expanded);
}, [expanded, onExpandChange]);
```

#### 6.2.2 Hover 交互处理

```
// Hover 处理
const { onMouseEnter, onMouseLeave } = useMultilevelCardHover({
  enabled: trigger === 'hover',
  delay: hoverDelay,
  onExpandChange: setExpanded,
});

// 注入到主卡片和次级面板
const enhancedCard = React.cloneElement(card, {
  onMouseEnter,
  onMouseLeave,
});
```

#### 6.2.3 次级面板渲染

```
// 次级面板组件
const panelElement = (
  <PanelWrapper
    visible={expanded}
    widthMode={panelWidthMode}
    width={panelWidth}
    style={panelStyle}
    className={panelClassName}
    animation={animation}
    animationDuration={animationDuration}
    onMount={onPanelMount}
    onUnmount={onPanelUnmount}
  >
    {panel}
  </PanelWrapper>
);

// Portal 渲染
const finalPanel = panelContainer
  ? ReactDOM.createPortal(panelElement, panelContainer)
  : panelElement;
```

#### 6.2.4 动画实现

```
// CSS 样式
.panel-wrapper {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-height 0.3s ease, opacity 0.3s ease;
}

.panel-wrapper--visible {
  max-height: 1000px; // 或动态计算
  opacity: 1;
}
```

### 6.3 组件结构图

```
MultilevelCard
├── CardWrapper
│   └── {card} (插槽)
│       └── 注入事件监听 (onClick/onMouseEnter/onMouseLeave)
│
└── PanelWrapper (conditional render based on expanded)
    ├── Portal (if panelContainer provided)
    │   └── {panel} (插槽)
    └── {panel} (插槽, default render)
```

### 6.4 依赖组件

#### 6.4.1 外部依赖

- React (核心库)
- ReactDOM (Portal 功能)
- classnames (类名合并)

#### 6.4.2 内部依赖

- PisellBasicCardProps (类型定义，来自 PisellCards/types.ts)

#### 6.4.3 可配合使用的组件

- TextCard
- GraphicTextCard
- TabCard
- 其他任意实现了 PisellBasicCardProps 的 Card 组件

### 6.5 边界情况处理

#### 6.5.1 主卡片未传入

- 不渲染任何内容，控制台警告

#### 6.5.2 次级面板未传入

- 只渲染主卡片，不显示次级面板

#### 6.5.3 panelContainer 无效

- 降级为默认渲染（不使用 Portal）

#### 6.5.4 主卡片不是 ReactElement

- 控制台警告，直接渲染 card

## 7. 组件通用性与可扩展性

### 7.1 通用性设计

- **插槽化**：主卡片和次级面板都通过插槽传入，不限制具体类型
- **无样式侵入**：组件本身不包含具体 UI 样式，只提供逻辑和布局
- **多场景适配**：支持多种触发方式、定位方式、宽度模式

### 7.2 扩展点

- **自定义触发逻辑**：可扩展更多触发方式（如长按、双击等）
- **自定义动画**：可通过 CSS 变量或动画库扩展更多动画效果
- **事件钩子**：提供丰富的回调函数，支持外部监听和控制
- **样式定制**：支持 className 和 style 的完全自定义

### 7.3 性能优化

- **条件渲染**：次级面板只在 expanded 时渲染
- **防抖处理**：hover 模式下的延迟处理，避免频繁触发
- **memo 优化**：子组件使用 React.memo 避免不必要的重渲染

## 8. 注意事项

### 8.1 开发注意事项

1. 主卡片必须是 ReactElement，否则无法注入事件监听
2. 使用 Portal 时，需确保 panelContainer 在 DOM 中已存在
3. 动画实现依赖 CSS transition，需注意浏览器兼容性
4. Hover 模式下的延迟时间需根据实际场景调整

### 8.2 使用注意事项

1. 主卡片需实现 PisellBasicCardProps 中的 active 属性（click 模式下）
2. 次级面板的宽度需根据实际场景设置，避免过宽或过窄
3. 使用 Portal 时，需注意样式继承和层级问题
4. 嵌套使用时，需注意事件冒泡问题

## 9. 开发计划

### 9.1 第一阶段：核心功能

- [ ] 实现 MultilevelCard 主组件
- [ ] 实现 CardWrapper 子组件
- [ ] 实现 PanelWrapper 子组件
- [ ] 实现 useMultilevelCardExpand Hook
- [ ] 实现基础样式

### 9.2 第二阶段：交互功能

- [ ] 实现 click 触发模式
- [ ] 实现 hover 触发模式
- [ ] 实现 useMultilevelCardHover Hook
- [ ] 实现防抖延迟

### 9.3 第三阶段：高级功能

- [ ] 实现 Portal 渲染
- [ ] 实现展开/收起动画
- [ ] 实现宽度模式配置
- [ ] 实现事件回调

### 9.4 第四阶段：优化与测试

- [ ] 性能优化
- [ ] 边界情况处理
- [ ] 单元测试
- [ ] 文档完善
