---
title: 开发文档
order: 1
category: pro
---

# Selector 组件开发文档

## 1. 组件的命名和存放位置

**组件名称**: `Selector`  
**存放位置**: `private-materials/src/pro/Selector/`  
**组件编号**: `pcp_de_selector`

## 2. 版本

**当前版本**: `1.0.1`  
**最后更新**: 2025-11-1

## 3. 相关文档

### 设计文档

- **Figma**: [Selector 组件设计稿](https://www.figma.com/design/pM8Ho6d7kCMv9vIBvFHUlj/Pisell-2.0-Component-Library?node-id=11247-1234)
- **飞书**: [Selector 组件需求文档](https://m1ed09stz4r.feishu.cn/wiki/NL5AwOs0ei5ZBekT4i6cuzIOnoM)

## 4. 组件规划

### 4.1 组件架构

#### Selector

```
Selector
  ├── useSelectionController (逻辑层)
  │     ├── state (values、errors、options)
  │     └── actions (状态操作、校验方法)
  ├── usePresetProps (预设配置转换层)
  │     ├── layout 计算 (flex/grid 布局配置)
  │     └── indicatorProps 增强 (自动应用 filled 等样式)
  ├── usePresetItemProps (预设选项配置层)
  │     ├── mediaProps 计算 (媒体选项配置)
  │     └── cardProps 计算 (卡片选项配置)
  └── content (内容渲染)
        ├── Title (分组标题)
        │     └── Tips (校验信息)
        └── OptionList (选项列表， 布局)
              ├── inject component (选中指示器/数量选择器)
              └── LayoutContainer (布局容器：flex/grid)
                  └── OptionItem (卡片、图文、自定义Radio/Checkbox)
                        └── slot ｜ children (插槽 ｜ 子元素)

```

#### SelectorGroup

```
SelectorGroup (容器组件)
  ├── 状态管理层 (useReducer)
  │     ├── values (选择值状态)
  │     ├── visibleGroups (分组可见性)
  │     └── effects (联动效果)
  │
  ├── 联动规则处理
  │     ├── normalizeRules (规则标准化)
  │     ├── computeEffects (计算联动效果)
  │     └── pruneValuesByAllowOnly (值裁剪)
  │
  └── 渲染层
        ├── CategoryTabs (标签页导航 - 可选)
        │     ├── 标签切换逻辑
        │     └── 滚动定位功能
        │
        └── IntersectionObserverProvider (视口观察容器)
              └── Content Container (内容容器)
                    └── IntersectionItem[] (观察项包装)
                          └── Selector (选择器组件)
                                ├── 验证规则处理
                                ├── 数据源动态禁用
                                ├── Title (标题 + 提示)
                                └── Options (选项列表)
```

### 4.2 选择模式支持

#### 基础模式

- **单选模式** : 只能选择一个选项
- **多选模式** : 可以选择多个选项

#### 高阶模式

- **单选+数量** : 单选基础上支持数量调节
- **多选+数量** : 多选基础上每个选项支持数量调节

### 4.3 预设系统（Preset System）

#### 设计目标

预设系统是 Selector 组件的**核心能力**，旨在极大简化低代码用户的配置复杂度。通过预定义的样式组合，用户无需深入了解底层的 `layout`、`itemProps`、`indicatorProps` 等复杂属性，仅通过一个简单的 `preset.variant` 值即可快速应用专业的展示形态。

**设计理念**：
- `preset={{ variant: '1' }}` 即可应用完整的布局和样式
- 预设提供默认配置，高级用户可通过手动配置覆盖
- 预设自动处理响应式布局（flex/grid），无需手动计算
- 预设基于设计规范，确保视觉一致性

#### 核心概念

预设系统基于两个核心配置项：

1. **variant** (组件级): `'default' | 'select' | 'card' | 'media'`
   - 决定选项的基础展示形态（默认样式 / 下拉选择器 / 卡片 / 图文）
   
2. **preset.variant** (预设编号): `'1' | '2' | '3' | '4' | '5'`
   - 在当前 variant 下选择具体的布局和样式方案

#### 预设变体编号说明（'1' - '5'）

不同的 `variant` 支持不同数量的预设变体：

| Variant   | 支持的预设变体 | 说明                                           |
| --------- | -------------- | ---------------------------------------------- |
| `default` | '1', '2', '3', '4' | 默认样式，支持单列/自适应布局的 4 种变体      |
| `select`  | '1'            | 下拉选择器样式，仅一种形态                     |
| `card`    | '1', '2', '3', '4', '5' | 卡片样式，支持最丰富的 5 种变体（固定宽度网格/单列等） |
| `media`   | '1', '2'       | 图文样式，支持 2 种变体（小尺寸/大尺寸）       |

**各变体的特点**：

- **'1'**: 基础布局，最常用的默认形态
- **'2'**: 增强样式（如自动应用 filled 指示器、添加边框等）
- **'3'**: 单列布局，适合横向卡片展示
- **'4'**: 大尺寸网格（325px），适合内容丰富的卡片
- **'5'**: 单列大卡片，适合详细信息展示

#### 预设系统的工作机制

预设系统通过两个核心 Hook 实现配置转换：

##### 1. usePresetProps - 组件级配置转换

**职责**：根据 `variant` 和 `preset.variant` 生成布局配置（layout）和指示器配置（indicatorProps）

**转换规则**：

```
// Layout 配置转换
const COL_CONFIG_MAP = {
  default: {
    '1': '100%',      // 单列布局
    '2': 'auto',      // Flex 自适应
    '3': '100%',      // 单列布局
    '4': 'auto',      // Flex 自适应
  },
  card: {
    '1': '209px',     // 固定宽度网格
    '2': '209px',     // 固定宽度网格 + filled 指示器
    '3': '100%',      // 单列布局
    '4': '325px',     // 大尺寸网格
    '5': '100%',      // 单列大卡片
  },
  media: {
    '1': 'auto',      // Flex 自适应
    '2': 'auto',      // Flex 自适应
  },
};

// 布局类型映射（使用 containerStyle 直接生成样式）
- '100%' → { containerStyle: { display: 'flex', flexDirection: 'column' } }  // 单列
- 'auto' → { containerStyle: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap' } }  // 自适应
- '209px'/'325px' → { containerStyle: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(209px, 100%), 1fr))' } }  // 自动适应网格
```

**IndicatorProps 自动增强**：
- `variant='media'` 时，自动应用 `indicatorProps={{ variant: 'filled' }}`
- `variant='card' && preset.variant='2'` 时，自动应用 `indicatorProps={{ variant: 'filled' }}`

##### 2. usePresetItemProps - 选项级配置转换

**职责**：根据预设生成具体的选项配置（mediaProps 或 cardProps）

**转换规则**：

**A. variant='default'**：
```
cardProps = {
  variant: 'borderless',
  layout: 'horizontal',
  selectedType: 'none',
  actionsPosition: 'left',
  actions: numberSelector ? [numberSelector] : [indicator],
}
```

**B. variant='media'**：
- `preset.variant='1'`:
  ```
  mediaProps = {
    size: 'small',
    showIndicator: false,
    shape: mode === 'single' ? 'circle' : 'square',
    coverType: 'text',
  }
  ```
- `preset.variant='2'`:
  ```
  mediaProps = {
    size: 'large',
    showIndicator: true,
    shape: 'circle',
  }
  ```

**C. variant='card'**：

横向布局（horizontal）：
- `preset.variant='1'`:
  ```
  cardProps = {
    styles: {
      container: { padding: 8, borderWidth: selected ? '2px' : '1px' },
      content: { textAlign: 'center', fontWeight: selected ? '600' : 'normal' }
    },
    actions: numberSelector ? [numberSelector] : [], // 不显示 indicator
  }
  ```
- `preset.variant='2'`:
  ```
  cardProps = {
    selectedType: 'filled',
    styles: { container: { padding: 8 } },
    actions: numberSelector ? [numberSelector] : [indicator],
  }
  ```
- `preset.variant='5'`:
  ```
  cardProps = {
    variant: 'borderless',
    selectedType: 'none',
    styles: { container: { padding: 8 } },
    actions: numberSelector ? [numberSelector] : [indicator],
  }
  ```

纵向布局（vertical）：
- `preset.variant='2'`:
  ```
  cardProps = {
    layout: 'vertical',
    variant: 'borderless',
    corner: indicator,  // indicator 放到右上角
    actions: numberSelector ? [numberSelector] : [],
  }
  ```
- 其他变体：
  ```
  cardProps = {
    layout: 'vertical',
    variant: 'borderless',
    actions: numberSelector ? [numberSelector] : [indicator],
  }
  ```

#### 预设系统接口定义

```
export interface PresetConfig {
  variant?: '1' | '2' | '3' | '4' | '5'; // 预设变体编号
}

export interface LayoutConfig {
  type?: 'antdGrid' | 'custom';  // 布局类型
  gutter?: number | object | Array<number>;  // 间距（可转换为 gap）
  containerStyle?: React.CSSProperties;  // 自定义容器样式（推荐使用）
  // antdGrid 模式专用配置
  columns?: number;
  align?: 'top' | 'middle' | 'bottom' | 'stretch';
  justify?: 'start' | 'end' | 'center' | 'space-between' | 'space-around';
  wrap?: boolean;
  colConfig?: { [key: string]: any };
  breakpoints?: { [key: string]: number };
}

export interface SelectorProps<T = OptionKey> extends GroupItem {
  // ... 其他属性
  preset?: PresetConfig; // 预设配置
  layout?: LayoutConfig; // 手动布局配置（优先级高于预设）
}
```

#### 预设与手动配置的优先级

预设系统采用**渐进增强**的设计原则：

1. **预设作为默认配置**：当仅提供 `preset` 时，组件使用预设的完整配置
2. **手动配置覆盖预设**：当同时提供 `preset` 和手动配置时，手动配置优先级更高
3. **深度合并策略**：对于 `styles` 等嵌套对象，使用 lodash merge 进行深度合并

**优先级顺序**：
```
自定义 renderItem > 手动 layout/itemProps/indicatorProps > preset 配置
```

**使用建议**：
- ✅ **低代码用户**：仅使用 `preset`，快速配置
- ✅ **设计师**：使用 `preset` + 少量 `itemProps.renderContent` 定制内容
- ✅ **高级开发者**：使用 `preset` 作为基础，通过手动配置精细调整
- ✅ **完全自定义**：使用 `renderItem` 完全控制渲染逻辑

## 5. 组件设计

### 5.1 主要接口

```
// 条件表达式
export interface ConditionExpr {
  groupId: string;
  op?: 'in' | 'notIn' | 'eq' | 'neq' | 'contains' | 'range' | 'exists';
  value?: any;
  values?: any[];
  min?: number;
  max?: number;
}

export interface ExprRule {
  // 逻辑组合
  and?: (ConditionExpr | ExprRule)[];
  or?: (ConditionExpr | ExprRule)[];
  not?: (ConditionExpr | ExprRule)[];
  // 别名
  allOf?: (ConditionExpr | ExprRule)[];
  anyOf?: (ConditionExpr | ExprRule)[];
  noneOf?: (ConditionExpr | ExprRule)[];
}

// 联动动作
export interface LinkageAction {
  type: 'show' | 'allowOnly' | 'requireOneOf';
  groupId: string;
  values?: any[];
  message?: string;
}

// 联动规则项
export interface LinkageRule {
  type?: 'expr';
  when: ExprRule | ConditionExpr;
  then: LinkageAction[];
  elseThen?: LinkageAction[];
  priority?: number;
  stopAfterApply?: boolean;
}

export type OptionKey = number | string;

export interface RuleConfig {
  autoValidate?: boolean; // 是否自动校验
  required?: 0 | 1; // 是否必填
  min?: number; // 最小选择数量（组级）
  max?: number; // 最大选择数量（组级）
  mutex?: Array<Array<OptionKey>>; // 组内互斥
  customValidator?: (value: SelectionValue) => Promise<void>; // 自定义异步校验器
}

export interface GroupItem {
  id?: number; // 分组id
  title?: string; // 分组标题
  mode?: 'single' | 'multiple';
  valueType?: 'primitive' | 'object'; //是否返回原始值，true时返回id或id[]，false时返回包含数量等信息的复合对象
  preserve?: boolean;
  fieldNames?: {
    label?: string; // 选项标题字段名
    value?: string; // 选项值字段名
    cover?: string; // 封面图片字段名
  };
  ruleConfig?: RuleConfig; // 校验规则
  dataSource?: OptionItem[]; // 选项列表
}

export interface OptionItem {
  id?: number; // 选项id
  title?: string; // 选项标题
  label?: string; // 选项标题
  value?: OptionKey; // 选项值
  cover?: string; // 封面图片
  groups?: GroupItem[]; // 选项组列表（预留）
  disabled?: boolean; // 是否禁用
  ruleConfig?: {
    min?: number; // 最小选择数量
    max?: number; // 最大选择数量
  }; // 选项校验规则
  [key: string]: any;
}

/** 原始值类型：单选时为id，多选时为id[] */
export type SelectionValuePrimitive<T = OptionKey> = T | T[];

/** 复杂对象值类型：包含id、数量等额外信息 */
export interface SelectionValueObject<T = OptionKey> {
  value?: T; // 选项值
  quantity?: number; // 选择数量
  [key: string]: any;
}

/** 单选值类型 */
export type SelectionValueSingle<T = OptionKey> = T | SelectionValueObject<T>;

/** 多选值类型 */
export type SelectionValueMultiple<T = OptionKey> =
  | T[]
  | SelectionValueObject<T>[];

/** 选择器值类型 */
export type SelectionValue<T = OptionKey> =
  | SelectionValueSingle<T>
  | SelectionValueMultiple<T>;

export type RenderItemProps<T = OptionKey> = {
  dataSource: OptionItem;
  actions: any;
  optionItemValue?: SelectionValue<T>;
  indicator: React.ReactNode;
  numberSelector: React.ReactNode;
};

export type ItemProps = {
  // card 类型子组件属性
  layout?: 'vertical' | 'horizontal'; // 默认水平方向
  selectedTypeVariant?: 'default' | 'bordered' | 'filled'; // 选中态样式变体
  // 自定义内容渲染
  renderContent?: (props: { dataSource: OptionItem }) => React.ReactNode;
  // 自定义样式
  styles?: {
    container?: React.CSSProperties;
    cover?: React.CSSProperties;
    label?: React.CSSProperties;
  };
  // indicator 相关配置, 需要配合layout使用
  // 卡片模式，横向布局时值为right，其他情况不生效
  // 卡片模式，垂直布局时值可以设置为topRight，bottom，其他情况不生效，当有数量选择器时，值必为topRight
  indicatorPosition?: 'left' | 'right' | 'bottom' | 'topRight';
  size?: 'small' | 'middle' | 'large' | number; // 针对图文模式，尺寸，默认large，当为数字时，表示px
  [key: string]: any;
};

export type GridConfig = {
  align?: 'top' | 'middle' | 'bottom' | 'stretch'; // 网格对齐方式
  columns?: number; // 网格列数
  gutter?: number | object | Array<number>; // 子元素间距
  justify?: 'start' | 'end' | 'center' | 'space-between' | 'space-around'; // 网格对齐方式
  wrap?: boolean; // 是否换行
  colConfig?: {
    [key: string]: any;
  }; // 列配置
};

export type LayoutConfig = {
  // 布局类型
  type?: 'antdGrid' | 'custom';
  // 自定义样式（推荐使用，更灵活）
  containerStyle?: React.CSSProperties;
  
  // 通用配置
  gutter?: number | object | Array<number>; // 子元素间距（可转换为 gap）
  
  // Ant Design Grid 专用（基础布局场景）
  columns?: number; // 网格列数
  align?: 'top' | 'middle' | 'bottom' | 'stretch'; // 网格对齐方式
  justify?: 'start' | 'end' | 'center' | 'space-between' | 'space-around'; // 网格对齐方式
  wrap?: boolean; // 是否换行
  colConfig?: {
    [key: string]: any;
  }; // 列配置（支持 Ant Design Col 的所有属性）
};

export interface PresetConfig {
  variant?: '1' | '2' | '3' | '4' | '5'; // 预设变体编号
}

export interface SelectorProps<T = OptionKey> extends GroupItem {
  // 值控制（支持 id、id[]、{id, quantity}、{id, quantity}[]）
  value?: SelectionValue<T>;
  defaultValue?: SelectionValue<T>;
  onChange?: (value: SelectionValue<T>) => void;
  onClear?: () => void;
  // 禁用状态
  disabled?: boolean;
  // UI 配置
  // 预设配置（简化低代码用户配置）
  preset?: PresetConfig;
  // 选项布局配置
  grid?: GridConfig;
  // 选项变体
  variant?: 'default' | 'select' | 'card' | 'media';
  // 自定义渲染函数
  renderItem?: (props: RenderItemProps<T>) => React.ReactNode;
  itemProps?: ItemProps;
  indicatorProps?: {
    show?: boolean; // 是否展示指示器 默认展示
    render?: (props: {
      option: OptionItem;
      selected: boolean;
      disabled: boolean;
      actions: any;
    }) => React.ReactNode; // 自定义指示器
  };
  // 是否展示数量选择器 默认auto: 根据选项的min和max判断
  showStepper?: true | false | 'auto';
  stepperProps?: {
    size?: 'small' | 'middle' | 'large';
    shape?: 'round' | 'square';
  };
  // 标题相关
  titleProps?: {
    visible?: boolean;
    style?: React.CSSProperties;
    title?: {
      visible?: boolean;
      text?: React.ReactNode;
      style?: React.CSSProperties;
    };
    icon?: {
      visible?: boolean;
      icon?: React.ReactNode;
      style?: React.CSSProperties;
    };
    tip?: {
      visible?: boolean;
      text?: React.ReactNode;
      style?: React.CSSProperties;
    };
  };
}
```

### 5.2 Props 详细说明

#### 基本属性

| 属性           | 类型                            | 默认值 | 说明                                                                                         | 示例                                                        |
| -------------- | ------------------------------- | ------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `id`           | `any`                           | -      | 组件标识符（可用于区分多个 Selector）                                                        | `1`                                                         |
| `title`        | `string`                        | -      | 组件标题文案                                                                                 | `'主食选择'`                                                |
| `dataSource`   | `OptionItem[]`                  | -      | 组件的数据源（选项列表）。`OptionItem.groups` 可预留嵌套，不参与当前渲染                     | `[{ value:1, label:'米饭' }, { value:2, label:'面条' }]`    |
| `value`        | `any \| any[]`                  | -      | 受控值。单选为 `value` 或 `{ value, quantity }`，多选为 `value[]` 或 `{ value, quantity }[]` | 单选：`1`；多选：`[1,3]`；带数量：`{ value:1, quantity:2 }` |
| `defaultValue` | `any \| any[]`                  | -      | 非受控初始值，形态与 `value` 一致                                                            | `[{ value:1, quantity:2 }]`                                 |
| `onChange`     | `(value: any \| any[]) => void` | -      | 选中变更回调。返回与 `value` 同形态                                                          | `onChange={(v) => console.log(v)}`                          |
| `onClear`      | `() => void`                    | -      | 清空当前选择的回调                                                                           | `onClear={() => setValue(undefined)}`                       |

#### 分组属性（GroupItem）

| 字段         | 类型                                                    | 默认值      | 说明                                  |
| ------------ | ------------------------------------------------------- | ----------- | ------------------------------------- |
| `mode`       | `'single' \| 'multiple'`                                | -           | 选择模式（单选/多选）                 |
| `valueType`  | `'primitive' \| 'object'`                               | -           | 值返回形态（原始值或对象）            |
| `preserve`   | `boolean`                                               | -           | 隐藏时是否保留值                      |
| `variant`    | `'default' \| 'select' \| 'card' \| 'media'`            | `'default'` | 展示变体                              |
| `fieldNames` | `{ label?: string; value?: string; cover?: string }`    | -           | 字段映射（自定义标题/值/封面字段名）  |

#### 预设配置 `preset`（低代码环境）

| 字段        | 类型                                        | 默认值 | 说明                                               |
| ----------- | ------------------------------------------- | ------ | -------------------------------------------------- |
| `variant`   | `'1' \| '2' \| '3' \| '4' \| '5'`           | `'1'`  | 预设变体编号（可选项由 variant 决定：default 支持 1-4，card 支持 1-5，media 支持 1-2，select 仅支持 1） |

#### 布局配置 `layout`（高级功能）

| 字段             | 类型                                                                     | 默认值 | 说明                                               |
| ---------------- | ------------------------------------------------------------------------ | ------ | -------------------------------------------------- |
| `type`           | `'antdGrid' \| 'custom'`                                                 | -      | 布局类型（antdGrid 使用 Ant Design Grid，custom 使用自定义样式） |
| `containerStyle` | `React.CSSProperties`                                                    | -      | 自定义容器样式（推荐使用，支持任意 CSS 布局）      |
| `gutter`         | `number \| object \| Array<number>`                                      | `0`    | 子元素间距（可转换为 gap）                         |
| `columns`        | `number`                                                                 | -      | 网格列数（仅 type='antdGrid' 时生效）              |
| `align`          | `'top' \| 'middle' \| 'bottom' \| 'stretch'`                             | -      | 垂直对齐（仅 type='antdGrid' 时生效）              |
| `justify`        | `'start' \| 'end' \| 'center' \| 'space-between' \| 'space-around'`      | -      | 水平对齐（仅 type='antdGrid' 时生效）              |
| `wrap`           | `boolean`                                                                | `true` | 是否换行（仅 type='antdGrid' 时生效）              |
| `colConfig`      | `object`                                                                 | -      | 列配置（仅 type='antdGrid' 时生效，如 `{ xs: 24, sm: 12, md: 8 }`） |

#### 规则配置 `ruleConfig`

| 字段              | 类型                       | 默认值 | 说明                                                                   |
| ----------------- | -------------------------- | ------ | ---------------------------------------------------------------------- |
| `autoValidate`    | `boolean`                  | -      | 是否自动校验                                                           |
| `required`        | `0 \| 1`                   | `0`    | 是否必选（在 Form 中配合校验）                                         |
| `min`             | `number`                   | -      | 最小选择数量（组级；多选或数量控件聚合后校验）                         |
| `max`             | `number`                   | -      | 最大选择数量（组级；多选或数量控件聚合后校验）                         |
| `mutex`           | `Array<Array<OptionKey>>`  | -      | 组内互斥规则                                                           |
| `customValidator` | `(value) => Promise<void>` | -      | 自定义异步校验器；`resolve` 通过，`reject({ type, message })` 写入错误 |

#### UI 配置

| 字段                               | 类型                                                                                         | 默认值         | 说明                                               |
| ---------------------------------- | -------------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------- |
| `renderItem`                       | `(props: RenderItemProps) => React.ReactNode`                                                | -              | 自定义渲染函数（优先级高于 variant）               |
| `itemProps.layout`                 | `'vertical' \| 'horizontal'`                                                                 | `'horizontal'` | 卡片内容排布方向                                   |
| `itemProps.selectedTypeVariant`    | `'default' \| 'bordered' \| 'filled'`                                                        | `'default'`    | 选中态样式变体                                     |
| `itemProps.renderContent`          | `(props: { dataSource: OptionItem }) => React.ReactNode`                                     | -              | 自定义内容渲染                                     |
| `itemProps.styles`                 | `{ container?: CSSProperties; cover?: CSSProperties; label?: CSSProperties }`                | -              | 自定义样式（容器/封面/标签）                       |
| `itemProps.indicatorPosition`      | `'left' \| 'right' \| 'bottom' \| 'topRight'`                                                | -              | 指示器位置（需配合 layout 使用）                   |
| `itemProps.size`                   | `'small' \| 'middle' \| 'large' \| number`                                                   | `'large'`      | 尺寸（图文模式，数字时表示 px）                    |
| `indicatorProps.show`              | `boolean`                                                                                    | `true`         | 是否展示指示器                                     |
| `indicatorProps.render`            | `(props: { option: OptionItem; selected: boolean; disabled: boolean; actions: any }) => ...` | -              | 自定义指示器渲染                                   |
| `stepperProps.size`                | `'small' \| 'middle' \| 'large'`                                                             | -              | 数量步进器尺寸                                     |
| `stepperProps.shape`               | `'round' \| 'square'`                                                                        | -              | 数量步进器形状                                     |

### 5.3 SelectorGroup 组件接口

SelectorGroup 是管理多个 Selector 组件的容器组件，支持联动规则、标签页导航和滚动监听等功能。

```
export interface SelectorGroupProps {
  dataSource: GroupItem[];
  linkageRules?: ExprRule[] | Record<string, any>; // 跨组规则集合（数组或语法糖对象）
  allowedValuesPolicy?: 'relaxed' | 'strict'; // 允许值集合处理策略
  preserve?: boolean;
  // 受控值能力
  value?: Record<string, SelectionValue>;
  defaultValue?: Record<string, SelectionValue>;
  onChange?: (values: Record<string, SelectionValue>) => void;
  tabProps?: {
    visible?: boolean;
  };
  customScrollParent?: HTMLElement | string;
  className?: string;
  style?: React.CSSProperties;
  [key: string]: any; // 其他自定义属性
}

export type SelectorGroupRef = {
  getValues: (includeHidden?: boolean) => Record<string, SelectionValue>;
  setValues: (values: Record<string, SelectionValue>) => void;
  validate: () => Promise<Record<string, never>>;
  reset: () => void;
};
```

#### SelectorGroup Props 说明

| 字段                  | 类型                                | 默认值     | 说明                               |
| --------------------- | ----------------------------------- | ---------- | ---------------------------------- |
| `dataSource`          | `GroupItem[]`                       | -          | 分组数据源                         |
| `linkageRules`        | `ExprRule[] \| Record<string, any>` | -          | 跨组联动规则（数组或语法糖对象）   |
| `allowedValuesPolicy` | `'relaxed' \| 'strict'`             | `'strict'` | 允许值集合处理策略                 |
| `preserve`            | `boolean`                           | `false`    | 隐藏分组是否保留值                 |
| `value`               | `Record<string, SelectionValue>`    | -          | 受控值（按分组 ID 组织）           |
| `defaultValue`        | `Record<string, SelectionValue>`    | -          | 默认值                             |
| `onChange`            | `(values) => void`                  | -          | 值变更回调                         |
| `tabProps.visible`    | `boolean`                           | `true`     | 是否显示标签页导航                 |
| `customScrollParent`  | `HTMLElement \| string`             | -          | 自定义滚动容器（元素或选择器）     |
| `className`           | `string`                            | -          | 自定义类名                         |
| `style`               | `React.CSSProperties`               | -          | 自定义样式                         |

#### 主题配置 `token`

| 字段              | 类型                          | 默认值        | 说明                                                                 |
| ----------------- | ----------------------------- | ------------- | -------------------------------------------------------------------- |
| `theme.token.colorPrimary` | `string`                      | 继承 antd 主题 | 覆盖组件的主题主色；组件内部通过 `theme.useToken()` 读取或外部传入   |

- 组件内部从 antd 引入 `theme`，解构：`const { useToken, getDesignToken } = theme`
- 主题主色获取策略：`props.theme?.token?.colorPrimary || useToken().token.colorPrimary`
- 使用 `getDesignToken({ token: { colorPrimary } })` 获取 `computedAntdTokens`
- 在组件最外层容器 `style` 注入 CSS 变量：`--color-primary: colorPrimary, --color-primary-bg = computedAntdTokens.colorPrimaryBg`
- 子组件或样式可通过css变量使用

### 5.4 Selector 组件设计

#### 5.4.1 概述

- 逻辑层：由 `useSelectionController` 提供状态、事件、数量校验与输出值归一化能力。
- 渲染层：消费逻辑层接口，负责具体 UI 呈现（`Card` / 自定义 `renderItem`）。
- 交互桥接：渲染层通过 `actions.toggle/update` 触发变更；逻辑层归一化并调用 `onChange`。

#### 5.4.2 useSelectionController

- 状态管理：当前选中集合、数量、错误信息（`state.errors`）。
- 事件处理：选择/取消、切换、清空、数量变更等交互（`actions.toggle/update/clear`）。
- 输出值构建：根据 `valueType` 与数量是否启用，构建外部 `value`。
- 校验器：`actions.validate()` 返回 `Promise<boolean>`，失败时写入 `state.errors`。

#### 5.4.3 Hook API

```
export interface SelectionOption extends OptionItem {
  _key: OptionKey;
  _label: string;
  selected: boolean;
  quantity: number;
}

export interface SelectionState {
  options: SelectionOption[];
  values: SelectionValue;
  showQuantity: boolean;
  errors: Array<{ type: 'required' | 'max' | 'min' | 'requireOneOf' ; message: string; _key?: OptionKey }>;
}

export interface SelectionActions {
  toggle: (optionKey: OptionKey) => void;
  update: (optionKey: OptionKey, data?: any) => void;
  clear: () => void;
  validate: () => Promise<boolean>;
}

export type SelectionValidator = () => boolean;

export function useSelectionController(params: {
  dataSource?: OptionItem[];
  ruleConfig?: RuleConfig;
  fieldNames?: { label?: string; value?: string };
  mode?: 'single' | 'multiple';
  valueType?: 'primitive' | 'object';
  value?: SelectionValuePrimitive | SelectionValue;
  defaultValue?: SelectionValuePrimitive | SelectionValue;
  onChange?: (value: SelectionValuePrimitive | SelectionValue) => void;
}): {
  state: SelectionState;
  actions: SelectionActions;
} {
  // 实现详见源码
}
```

#### 5.4.4 事件流与受控/非受控

- 非受控：内部维护 `state`；`actions` 更新状态并回调 `onChange`。
- 受控：优先以外部 `value` 为准；`actions` 仅触发 `onChange`，由父级决定是否更新传入的 `value`。

#### 5.4.5 校验逻辑（更新）

- 基础校验：
  - `required`：必须至少选择 1 项（或数量满足最小要求）。
  - `min/max`：多选数量限制；带数量时按实际数量聚合后校验。
- 规则扩展：组间联动规则由上层 `BatchSelector` 的 `linkageRules` 属性统一管理（不再在 `GroupItem` 的 `ruleConfig` 中定义）。
- 对外接口：组件通过 `ref.validate()` 暴露校验方法，返回 `Promise<boolean>`；错误信息可从 `state.errors` 读取（参考第 7 章示例）。

- 自定义校验：`ruleConfig.customValidator?: (value) => Promise<void>`，`resolve` 表示通过；`reject` 表示失败，建议抛出 `{ type: 'requireOneOf' | 'custom', message?: string }`，组件会把错误写入 `state.errors`。

#### 5.4.6 扩展能力

自定义子组件需要实现以下接口

```
interface RenderItemProps<T = OptionKey> {
  dataSource: OptionItem;
  actions: any;
  optionItemValue?: SelectionValue<T>;
  indicator: React.ReactNode; // 单选/多选指示器
  numberSelector: React.ReactNode; // 数量步进器
}
```

#### 5.4.7 简易示例（骨架伪代码）

```
function Selector(props, ref) {
  const { dataSource = [], ruleConfig, mode, valueType, fieldNames, appearance, disabled } = props;
  const { state, actions } = useSelectionController({ dataSource, ruleConfig, mode, valueType, fieldNames, ...props });
  useImperativeHandle(ref, () => ({ validate: () => actions.validate() }));

  return (
    <div>
      {(state.options || []).map((opt) => (
        <CardItem
          key={opt._key}
          selected={opt.selected}
          disabled={disabled}
          indicator={/* 单选用 Radio；多选用 Checkbox */}
          numberSelector={/* 仅在启用数量且选中时显示 */}
          onClick={() => actions.toggle(opt._key)}
        />
      ))}
    </div>
  );
}
```

## 6. 快速开始

### 6.1 使用预设系统（推荐 ⭐）

预设系统是最简单的使用方式，仅需配置 `variant` 和 `preset.variant` 即可快速实现专业的展示效果。

#### 6.1.1 Default Variant - 默认样式

**适用场景**：常规的选项列表，文本为主的选择场景

```
// Preset 1 - 单列布局
<Selector
  title="主食选择"
  dataSource={[
    { value: 1, label: '米饭' },
    { value: 2, label: '面条' },
    { value: 3, label: '馒头' },
  ]}
  mode="single"
  variant="default"
  preset={{ variant: '1' }}  // 仅需这一行配置
  value={1}
  onChange={(value) => console.log(value)}
/>

// Preset 2 - 自适应宽度
<Selector
  title="口味选择"
  dataSource={[
    { value: 1, label: '麻辣' },
    { value: 2, label: '酸辣' },
    { value: 3, label: '甜味' },
  ]}
  mode="single"
  variant="default"
  preset={{ variant: '2' }}  // Flex 自适应布局
  value={1}
  onChange={(value) => console.log(value)}
/>
```

#### 6.1.2 Card Variant - 卡片样式

**适用场景**：套餐选择、商品规格、配置选项等需要卡片展示的场景

```
// Preset 1 - 固定宽度网格（209px）
<Selector
  title="选择套餐"
  mode="single"
  variant="card"
  preset={{ variant: '1' }}
  dataSource={[
    { value: 1, label: '基础版', price: '¥99/月' },
    { value: 2, label: '专业版', price: '¥299/月' },
    { value: 3, label: '企业版', price: '¥999/月' },
  ]}
  itemProps={{
    renderContent: ({ dataSource }) => (
      <div>
        <div style={{ fontSize: 16, fontWeight: 600 }}>{dataSource.label}</div>
        <div style={{ color: '#ff4d4f', marginTop: 8 }}>{dataSource.price}</div>
      </div>
    ),
  }}
  value={1}
  onChange={(value) => console.log(value)}
/>

// Preset 2 - 固定宽度 + Filled 指示器
<Selector
  title="选择配送方式"
  mode="single"
  variant="card"
  preset={{ variant: '2' }}  // 自动应用 filled 风格指示器
  dataSource={[
    { value: 1, label: '标准配送', desc: '3-5个工作日送达' },
    { value: 2, label: '加急配送', desc: '1-2个工作日送达' },
  ]}
  value={1}
  onChange={(value) => console.log(value)}
/>

// Preset 3 - 单列横向卡片
<Selector
  title="支付方式"
  mode="single"
  variant="card"
  preset={{ variant: '3' }}
  dataSource={[
    { value: 1, label: '支付宝', icon: '💳' },
    { value: 2, label: '微信支付', icon: '💚' },
  ]}
  itemProps={{
    layout: 'horizontal',
    indicatorPosition: 'right',
    renderContent: ({ dataSource }) => (
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <span style={{ fontSize: 24 }}>{dataSource.icon}</span>
        <span>{dataSource.label}</span>
      </div>
    ),
  }}
  value={1}
  onChange={(value) => console.log(value)}
/>

// Preset 4 - 大尺寸网格（325px）
<Selector
  title="会员套餐"
  mode="single"
  variant="card"
  preset={{ variant: '4' }}
  dataSource={[
    { 
      value: 1, 
      label: 'VIP会员', 
      price: '¥199/年',
      desc: '享受全站95折优惠',
      features: ['5GB 存储', '基础功能']
    },
  ]}
  itemProps={{
    renderContent: ({ dataSource }) => (
      <div>
        <div style={{ fontSize: 20, fontWeight: 600 }}>{dataSource.label}</div>
        <div style={{ color: '#ff4d4f', fontSize: 24, margin: '12px 0' }}>
          {dataSource.price}
        </div>
        <div style={{ color: '#666', marginBottom: 12 }}>{dataSource.desc}</div>
        {dataSource.features.map((f, i) => (
          <div key={i}>✓ {f}</div>
        ))}
      </div>
    ),
  }}
  value={1}
  onChange={(value) => console.log(value)}
/>

// Preset 5 - 单列大卡片
<Selector
  title="商品详情"
  mode="single"
  variant="card"
  preset={{ variant: '5' }}
  dataSource={[
    { value: 1, label: '商品A', price: '¥999', desc: '详细描述信息' },
  ]}
  itemProps={{
    layout: 'horizontal',
    indicatorPosition: 'right',
  }}
  value={1}
  onChange={(value) => console.log(value)}
/>
```

#### 6.1.3 Media Variant - 图文样式

**适用场景**：图片选择、图库、相册等以图片为主的场景

```
// Preset 1 - 小尺寸自适应
<Selector
  title="选择图片类型"
  mode="multiple"
  variant="media"
  preset={{ variant: '1' }}
  dataSource={[
    { value: 1, label: '风景', cover: 'https://picsum.photos/300/200' },
    { value: 2, label: '人物', cover: 'https://picsum.photos/300/200' },
    { value: 3, label: '建筑', cover: 'https://picsum.photos/300/200' },
  ]}
  fieldNames={{ value: 'value', label: 'label', cover: 'cover' }}
  value={[1]}
  onChange={(value) => console.log(value)}
/>

// Preset 2 - 大尺寸 + 显示指示器
<Selector
  title="选择封面图"
  mode="single"
  variant="media"
  preset={{ variant: '2' }}
  dataSource={[
    { value: 1, label: '图片A', cover: 'https://picsum.photos/400/300' },
    { value: 2, label: '图片B', cover: 'https://picsum.photos/400/300' },
  ]}
  fieldNames={{ value: 'value', label: 'label', cover: 'cover' }}
  value={1}
  onChange={(value) => console.log(value)}
/>
```

#### 6.1.4 带数量选择的预设

```
// 单选 + 数量
<Selector
  title="选择饮品"
  mode="single"
  variant="card"
  valueType="object"
  showStepper={true}
  preset={{ variant: '1' }}
  dataSource={[
    { value: 1, label: '可乐', price: 5, ruleConfig: { min: 1, max: 10 } },
    { value: 2, label: '橙汁', price: 8, ruleConfig: { min: 1, max: 5 } },
  ]}
  value={{ value: 1, quantity: 2 }}
  onChange={(value) => console.log(value)}
/>

// 多选 + 数量
<Selector
  title="选择配菜"
  mode="multiple"
  variant="card"
  valueType="object"
  showStepper={true}
  preset={{ variant: '2' }}
  dataSource={[
    { value: 1, label: '青菜', ruleConfig: { min: 0, max: 3 } },
    { value: 2, label: '豆腐', ruleConfig: { min: 0, max: 2 } },
  ]}
  value={[{ value: 1, quantity: 2 }]}
  onChange={(value) => console.log(value)}
/>
```

### 6.2 基础用法（不使用预设）

如果不使用预设，可以手动配置所有属性：

```
// 最简单的用法
<Selector
  title="主食选择"
  dataSource={[
    { value: 1, label: '米饭' },
    { value: 2, label: '面条' },
  ]}
  mode="single"
  value={1}
  onChange={(value) => console.log(value)}
/>
```

### 6.3 SelectorGroup 使用示例

```
// 多分组管理（带联动规则）
<SelectorGroup
  dataSource={[
    {
      id: 1,
      title: '主食',
      mode: 'single',
      valueType: 'primitive',
      ruleConfig: { required: 1 },
      dataSource: [
        { value: 1, label: '米饭' },
        { value: 2, label: '面条' },
        { value: 3, label: '馒头' },
      ],
    },
    {
      id: 2,
      title: '配菜',
      mode: 'multiple',
      valueType: 'object',
      ruleConfig: { min: 1, max: 3 },
      dataSource: [
        { value: 1, label: '青菜', ruleConfig: { min: 0, max: 2 } },
        { value: 2, label: '豆腐', ruleConfig: { min: 0, max: 1 } },
      ],
    },
    {
      id: 3,
      title: '饮品',
      mode: 'single',
      valueType: 'primitive',
      dataSource: [
        { value: 1, label: '可乐' },
        { value: 2, label: '橙汁' },
      ],
    },
  ]}
  linkageRules={[
    {
      // 选择米饭时显示饮品分组
      type: 'expr',
      when: { and: [{ groupId: '1', op: 'contains', value: 1 }] },
      then: [
        { type: 'show', groupId: '3' },
      ],
    },
    {
      // 选择面条时限制配菜选项和必选验证
      type: 'expr',
      when: { and: [{ groupId: '1', op: 'contains', value: 2 }] },
      then: [
        { type: 'allowOnly', groupId: '2', values: [1] }, // 只能选青菜
        { type: 'requireOneOf', groupId: '2', values: [1], message: '面条必须配青菜' },
      ],
    },
  ]}
  tabProps={{ visible: true }}
  value={{
    '1': 1,
    '2': [{ value: 1, quantity: 1 }],
  }}
  onChange={(values) => console.log('所有分组值:', values)}
/>
```

### 6.4 嵌套数据源（预留功能）

```
// 嵌套数据（OptionItem.groups 预留，当前版本不参与渲染）
<Selector
  id="6"
  title="饮料"
  dataSource={[
    { value: '11', label: '可乐' },
    { value: '12', label: '咖啡' },
    {
      value: '13',
      label: '奶茶',
      groups: [
        {
          title: '规格',
          dataSource: [
            { value: '1311', label: '中' },
            { value: '1312', label: '大' },
          ],
        },
        {
          title: '温度',
          dataSource: [
            { value: '1321', label: '热' },
            { value: '1322', label: '冰' },
          ],
        },
      ],
    },
  ]}
  mode="single"
  valueType="object"
  value={{ value: '13' }}
  onChange={(val) => console.log(val)}
  titleProps={{
    visible: true,
    title: { text: '饮料' },
  }}
/>

// 注意：`groups` 为预留字段，当前版本不会递归渲染内部分组，只以普通选项显示。
```

## 7. 高级功能（Advanced）

当预设系统无法满足需求时，可以使用以下高级功能进行精细控制。

### 7.1 手动配置布局（Layout）

不使用预设时，可以通过 `layout` 属性手动配置布局。推荐使用 `containerStyle` 直接传入 CSS 样式，更灵活：

```
// Flex 布局 - 行方向自适应（推荐）
<Selector
  dataSource={dataSource}
  layout={{
    containerStyle: {
      display: 'flex',
      flexDirection: 'row',
      flexWrap: 'wrap',
    },
    gutter: 16,  // 便捷配置，会转换为 gap
  }}
/>

// Flex 布局 - 列方向（单列）
<Selector
  dataSource={dataSource}
  layout={{
    containerStyle: {
      display: 'flex',
      flexDirection: 'column',
    },
    gutter: [16, 16],
  }}
/>

// CSS Grid 布局 - 自动适应列数（推荐）
<Selector
  dataSource={dataSource}
  layout={{
    containerStyle: {
      display: 'grid',
      gridTemplateColumns: 'repeat(auto-fit, minmax(min(209px, 100%), 1fr))',
    },
    gutter: 16,
  }}
/>

// CSS Grid 布局 - 大尺寸卡片
<Selector
  dataSource={dataSource}
  layout={{
    containerStyle: {
      display: 'grid',
      gridTemplateColumns: 'repeat(auto-fit, minmax(min(325px, 100%), 1fr))',
    },
    gutter: 16,
  }}
/>

// Ant Design Grid 布局 - 响应式栅格
<Selector
  dataSource={dataSource}
  layout={{
    type: 'antdGrid',
    columns: 3,
    gutter: [16, 16],
    align: 'top',
    justify: 'start',
    colConfig: { xs: 24, sm: 12, md: 8, lg: 6 },
  }}
/>
```

**布局类型说明**：
- **Custom 布局（默认）**：使用 `containerStyle` 传入任意 CSS 样式
  - **Flex 布局**：适合内容宽度不固定，需要自适应的场景
    - `display: 'flex'` + `flexDirection: 'row'`：横向排列
    - `display: 'flex'` + `flexDirection: 'column'`：纵向排列（单列）
    - `flexWrap: 'wrap'`：自动换行
  - **CSS Grid 布局**：适合固定宽度的响应式网格
    - 使用 `gridTemplateColumns` 定义列宽
    - 响应式原理：`repeat(auto-fit, minmax(min(基础宽度, 100%), 1fr))`
- **Ant Design Grid 布局**：使用 Ant Design 的栅格系统
  - 适合需要使用标准响应式断点的场景
  - 通过 `columns`、`colConfig` 等配置响应式布局
  - 支持 `align`、`justify` 等对齐属性

### 7.2 自定义选项内容（ItemProps）

通过 `itemProps` 可以精细控制每个选项的展示：

#### 7.2.1 Card 变体的自定义

```
<Selector
  variant="card"
  itemProps={{
    // 布局方向
    layout: 'vertical',  // 'vertical' | 'horizontal'
    
    // 选中态样式
    selectedTypeVariant: 'filled',  // 'default' | 'bordered' | 'filled'
    
    // 指示器位置
    indicatorPosition: 'topRight',  // 'left' | 'right' | 'bottom' | 'topRight'
    
    // 自定义内容渲染
    renderContent: ({ dataSource }) => (
      <div>
        <h3>{dataSource.label}</h3>
        <p>{dataSource.description}</p>
      </div>
    ),
    
    // 自定义样式
    styles: {
      container: { padding: 16, borderRadius: 8 },
      cover: { width: 100, height: 100 },
      label: { fontSize: 16, fontWeight: 600 },
    },
  }}
/>
```

#### 7.2.2 Media 变体的自定义

```
<Selector
  variant="media"
  itemProps={{
    // 尺寸
    size: 'large',  // 'small' | 'middle' | 'large' | number(px)
    
    // 形状
    shape: 'circle',  // 'circle' | 'square'
    
    // 是否显示指示器
    showIndicator: true,
    
    // 封面类型
    coverType: 'image',  // 'image' | 'text'
  }}
/>
```

#### 7.2.3 样式深度合并

`itemProps.styles` 使用 lodash merge 进行深度合并，可以在预设基础上微调：

```
<Selector
  variant="card"
  preset={{ variant: '1' }}
  itemProps={{
    // 这些样式会与预设样式深度合并
    styles: {
      container: { 
        padding: 12,  // 覆盖预设的 padding: 8
        borderRadius: 8,  // 新增属性
      },
    },
  }}
/>
```

### 7.3 自定义指示器（IndicatorProps）

```
<Selector
  indicatorProps={{
    // 是否显示指示器
    show: true,
    
    // 指示器样式变体
    variant: 'filled',  // 'default' | 'filled'
    
    // 自定义指示器渲染
    render: ({ option, selected, disabled, actions }) => (
      <div onClick={() => !disabled && actions.toggle(option._key)}>
        {selected ? '✓' : '○'}
      </div>
    ),
  }}
/>
```

### 7.4 自定义数量选择器（StepperProps）

```
<Selector
  showStepper={true}
  stepperProps={{
    // 尺寸
    size: 'middle',  // 'small' | 'middle' | 'large'
    
    // 形状
    shape: 'round',  // 'round' | 'square'
  }}
/>
```

### 7.5 完全自定义渲染（RenderItem）

当需要完全自定义选项的渲染逻辑时，可以使用 `renderItem`：

```
<Selector
  renderItem={({ dataSource, actions, indicator, numberSelector, optionItemValue }) => {
    const selected = !!optionItemValue;
    
    return (
      <div
        onClick={() => actions.toggle(dataSource._key)}
        style={{
          padding: 16,
          border: `2px solid ${selected ? '#1890ff' : '#d9d9d9'}`,
          borderRadius: 8,
          cursor: 'pointer',
        }}
      >
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          {indicator}
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 16, fontWeight: 600 }}>{dataSource.label}</div>
            <div style={{ color: '#999' }}>{dataSource.description}</div>
          </div>
          {numberSelector}
        </div>
      </div>
    );
  }}
/>
```

**RenderItemProps 说明**：
- `dataSource`: 当前选项的数据
- `actions`: 操作方法（toggle/update/clear）
- `indicator`: 系统生成的指示器（Radio/Checkbox）
- `numberSelector`: 系统生成的数量选择器（仅在 showStepper=true 时有值）
- `optionItemValue`: 当前选项的值（判断是否选中）

### 7.6 手动配置与预设混合使用

预设和手动配置可以混合使用，手动配置优先级更高：

```
<Selector
  variant="card"
  preset={{ variant: '1' }}  // 使用预设作为基础
  
  // 以下手动配置会覆盖预设配置
  indicatorProps={{
    variant: 'filled',  // 覆盖预设的指示器样式
  }}
  
  itemProps={{
    selectedTypeVariant: 'bordered',  // 覆盖预设的选中态
    styles: {
      container: { padding: 16 },  // 深度合并到预设样式
    },
  }}
/>
```

**优先级顺序**（从高到低）：
1. `renderItem` - 完全自定义渲染
2. 手动配置的 `layout`、`itemProps`、`indicatorProps`
3. `preset` 配置
4. 组件默认值

### 7.7 自定义标题配置

```
<Selector
  title="选择配置"
  titleProps={{
    visible: true,  // 是否显示标题区域
    
    // 标题文本配置
    title: {
      visible: true,
      text: '自定义标题',  // 覆盖 props.title
      style: { fontSize: 18, fontWeight: 600 },
    },
    
    // 图标配置
    icon: {
      visible: true,
      icon: <StarOutlined />,
      style: { color: '#faad14' },
    },
    
    // 提示文本配置
    tip: {
      visible: true,
      text: '请至少选择一项',
      style: { color: '#999' },
    },
    
    // 整体样式
    style: { marginBottom: 16 },
  }}
/>
```

### 7.8 禁用状态控制

```
// 整体禁用
<Selector
  disabled={true}
  dataSource={dataSource}
/>

// 单个选项禁用
<Selector
  dataSource={[
    { value: 1, label: '可选项' },
    { value: 2, label: '禁用项', disabled: true },
  ]}
/>
```

### 7.9 字段映射（FieldNames）

当数据源字段名不是 `value`、`label`、`cover` 时，可以通过 `fieldNames` 映射：

```
<Selector
  dataSource={[
    { id: 1, name: '选项A', image: 'https://...' },
    { id: 2, name: '选项B', image: 'https://...' },
  ]}
  fieldNames={{
    value: 'id',
    label: 'name',
    cover: 'image',
  }}
/>
```

## 8. 最佳实践

### 8.1 如何选择合适的预设

#### 8.1.1 根据 Variant 选择

| Variant   | 适用场景                                 | 推荐预设 | 说明                                           |
| --------- | ---------------------------------------- | -------- | ---------------------------------------------- |
| `default` | 纯文本选项、标签选择、简单列表           | '1', '2' | 最简单的样式，适合快速选择                     |
| `select`  | 下拉选择器样式（表单场景）               | '1'      | 仅一种形态                                     |
| `card`    | 套餐、商品规格、配置选项、复杂信息展示   | '1'-'5'  | 支持最丰富的变体，适合需要对比的场景           |
| `media`   | 图片选择、图库、相册、视觉优先的内容     | '1', '2' | 专为图片内容优化                               |

#### 8.1.2 根据选项数量选择

- **1-3 个选项**：推荐 `default` + `preset={{ variant: '2' }}`，自适应宽度横向排列
- **4-6 个选项**：推荐 `card` + `preset={{ variant: '1' }}`，固定宽度网格
- **6+ 个选项**：推荐 `card` + `preset={{ variant: '3' }}`，单列布局便于滚动

#### 8.1.3 根据内容复杂度选择

- **纯文本**：`default` + `preset={{ variant: '1' }}`
- **文本 + 图标**：`default` + `preset={{ variant: '2' }}` + 自定义 renderContent
- **图片 + 文本**：`media` + `preset={{ variant: '2' }}`
- **标题 + 描述**：`card` + `preset={{ variant: '3' }}`
- **完整信息卡片**：`card` + `preset={{ variant: '4' }}` 或 `'5'`

#### 8.1.4 根据场景选择

| 场景               | Variant   | Preset       | ItemProps                              |
| ------------------ | --------- | ------------ | -------------------------------------- |
| 口味选择           | `default` | `'2'`        | -                                      |
| 支付方式           | `card`    | `'3'`        | `{ layout: 'horizontal' }`             |
| 套餐对比           | `card`    | `'4'`        | 自定义 `renderContent`                 |
| 商品规格           | `card`    | `'1'`        | `{ layout: 'vertical' }`               |
| 图片选择           | `media`   | `'1'`        | -                                      |
| 配菜（带数量）     | `card`    | `'2'`        | `showStepper={true}`                   |

#### 8.1.5 预设与手动配置的选择策略

| 用户类型     | 推荐方式           | 原因                                   |
| ------------ | ------------------ | -------------------------------------- |
| 低代码用户   | 仅使用预设         | 简单快捷，无需了解底层属性             |
| 设计师       | 预设 + renderContent | 预设提供布局，renderContent 定制内容   |
| 前端开发者   | 预设 + 手动覆盖    | 基于预设，通过手动配置精细调整         |
| 高级定制     | renderItem         | 完全自定义渲染逻辑                     |

#### 8.1.6 常见场景预设组合

**电商场景**：
```
// 商品规格选择
<Selector
  variant="card"
  preset={{ variant: '1' }}
  itemProps={{
    layout: 'vertical',
    indicatorPosition: 'topRight',
  }}
/>

// 支付方式选择
<Selector
  variant="card"
  preset={{ variant: '3' }}
  itemProps={{
    layout: 'horizontal',
    indicatorPosition: 'right',
  }}
/>

// 增值服务（多选）
<Selector
  mode="multiple"
  variant="card"
  preset={{ variant: '3' }}
  itemProps={{
    layout: 'horizontal',
    indicatorPosition: 'left',
  }}
/>
```

**问卷调查场景**：
```
// 单选题
<Selector
  mode="single"
  variant="default"
  preset={{ variant: '1' }}
/>

// 多选题
<Selector
  mode="multiple"
  variant="default"
  preset={{ variant: '1' }}
/>
```

**餐饮点餐场景**：
```
// 主食选择
<Selector
  mode="single"
  variant="card"
  preset={{ variant: '1' }}
/>

// 配菜（带数量）
<Selector
  mode="multiple"
  variant="card"
  valueType="object"
  showStepper={true}
  preset={{ variant: '2' }}
/>

// 饮品（单选带数量）
<Selector
  mode="single"
  variant="card"
  valueType="object"
  showStepper={true}
  preset={{ variant: '1' }}
/>
```

**图片选择场景**：
```
// 图片类型选择（多选）
<Selector
  mode="multiple"
  variant="media"
  preset={{ variant: '1' }}
/>

// 封面图选择（单选）
<Selector
  mode="single"
  variant="media"
  preset={{ variant: '2' }}
/>
```

### 8.2 套餐商品 SKU 选择

```
import React, { useEffect, useRef } from 'react';
import { SelectorGroup } from '@pisell/private-materials';
import { Form, Button } from 'antd';

const BundleProductSelector = ({ dataSource, value = {}, onChange }) => {
  const ref = useRef<SelectorGroup>(null);

  return (
    <Form.Item
      rules={[
        {
          validator: async () => {
            try {
              await ref.current?.validate?.();
              return Promise.resolve();
            } catch (errors) {
              return Promise.reject(new Error('请完成必填项'));
            }
          },
        },
      ]}
    >
      <SelectorGroup
        ref={ref}
        dataSource={dataSource}
        value={value}
        onChange={onChange}
        tabProps={{ visible: true }}
        linkageRules={[
          {
            // 示例：选择特定主食时显示特定配菜
            type: 'expr',
            when: { and: [{ groupId: '1', op: 'contains', value: 1 }] },
            then: [
              { type: 'show', groupId: '2' },
              { type: 'allowOnly', groupId: '2', values: [1, 2, 3] },
            ],
          },
        ]}
      />
    </Form.Item>
  );
};

const BundleProductDetail = ({ dataSource, value = {}, onChange }) => {
  const [form] = Form.useForm();

  const handleSubmit = async () => {
    const values = await form.validateFields();
    onChange?.(values);
  };

  return (
    <div>
      <Form form={form}>
        <BundleProductSelector
          dataSource={dataSource}
          value={value}
          onChange={onChange}
        />
      </Form>
      <Button type="primary" onClick={handleSubmit}>
        提交
      </Button>
    </div>
  );
};
```

## 附录：联动规则详细说明

### A.1 联动规则（linkageRules）

联动规则使用 `when: ConditionExpr` + `then: Action[]` 的结构来定义条件和动作。

#### A.1.1 条件表达式（ConditionExpr）

递归结构：`ExprRule` 支持在 `allOf/anyOf/noneOf`（或别名 `and/or/not`）中嵌套 `ConditionExpr` 或子 `ExprRule`。

**字段别名（推荐）**：
- `and` = `allOf`
- `or` = `anyOf` 
- `not` = `noneOf`

**条件表达式扩展**：

```
ConditionExpr: { 
  groupId: string, 
  op?: 'in'|'notIn'|'eq'|'neq'|'contains'|'range'|'exists', 
  value?: any, 
  values?: any[], 
  min?: number, 
  max?: number 
}
```

**语义说明**：
- `in`: 任一命中（集合有交集）
- `notIn`: 全部不命中（集合无交集）
- `eq`: 选择包含指定 `value`
- `neq`: 选择不包含指定 `value`
- `contains`: 等价于 `eq`，用于表意"包含某项"
- `range`: 选择数量在 `[min,max]` 区间（闭区间）
- `exists`: 当前分组已选择（数量>0）

#### A.1.2 动作（Action）

- `show`: `{ type: 'show', groupId }`
- `allowOnly`: `{ type: 'allowOnly', groupId, values }`
- `requireOneOf`: `{ type: 'requireOneOf', groupId, values, message?: string }`

> **分组可见性约定**：凡在 `show` 中出现过的分组视为"受控分组"，默认隐藏；每次重算前先复位为隐藏，再应用命中的 `show`。

#### A.1.3 规则项（Rule）扩展

```
Rule: { 
  type?: 'expr', 
  when: ExprRule | ConditionExpr, 
  then: Action[], 
  elseThen?: Action[], 
  priority?: number, 
  stopAfterApply?: boolean 
}
```

- `elseThen`: 当 `when` 不满足时应用的动作，避免书写"反向规则"。
- `stopAfterApply`: 当规则匹配并应用后，短路停止继续处理后续规则（按 `priority` 排序后的顺序）。

### A.2 联动规则示例

#### A.2.1 基础示例

**当 `size=small` 时，`heat` 仅允许指定集合 `['room','hot','veryHot']`**

```
{
  type: 'expr',
  when: { and: [{ groupId: 'size', op: 'in', values: ['small'] }] }, // and 别名（等价 allOf）
  then: [{ type: 'allowOnly', groupId: 'heat', values: ['room','hot','veryHot'] }],
  priority: 20
}
```

**当 `size=small` 时，`heat` 必须选择指定集合中的一个 `['room','hot','veryHot']`**

```
{
  type: 'expr',
  when: { and: [{ groupId: 'size', op: 'in', values: ['small'] }] },
  then: [{ type: 'requireOneOf', groupId: 'heat', values: ['room','hot','veryHot'], message: '小杯必须选择 常温/热/烫 之一' }],
  priority: 30
}
```

**选了汉堡则出现汉堡的定制，选择 pizza 则出现 pizza 的定制**

```
{
  type: 'expr',
  when: { and: [{ groupId: 'main', op: 'eq', value: 'burger' }] },
  then: [{ type: 'show', groupId: 'burger-custom' }],
  priority: 10
}
{
  type: 'expr',
  when: { and: [{ groupId: 'main', op: 'eq', value: 'pizza' }] },
  then: [{ type: 'show', groupId: 'pizza-custom' }],
  priority: 10
}
```

#### A.2.2 复杂嵌套与分支示例

**当 `size` 为 `small|large` 且（`main=burger` 或 `heat in [hot,veryHot]`），同时 `sugar` 不为 `high` 时显示 `burger-custom`，否则显示 `default-custom`，并在命中后短路：**

```
{
  type: 'expr',
  when: {
    and: [
      { groupId: 'size', op: 'in', values: ['small', 'large'] },
      { or: [
        { groupId: 'main', op: 'eq', value: 'burger' }, 
        { groupId: 'heat', op: 'in', values: ['hot','veryHot'] }
      ] },
      { not: [{ groupId: 'sugar', op: 'in', values: ['high'] }] }
    ]
  },
  then: [{ type: 'show', groupId: 'burger-custom' }],
  elseThen: [{ type: 'show', groupId: 'default-custom' }],
  priority: 15,
  stopAfterApply: true
}
```

**范围与存在判断：当 `toppings` 选择数量在 2 到 4 之间，且包含 `cheese`，要求至少选一个 `sauce`。**

```
{
  type: 'expr',
  when: { 
    and: [
      { groupId: 'toppings', op: 'range', min: 2, max: 4 }, 
      { groupId: 'toppings', op: 'contains', value: 'cheese' }
    ]
  },
  then: [{ type: 'requireOneOf', groupId: 'sauce', values: ['ketchup','mustard','bbq'] }],
  priority: 25
}
```
