---
nav:
  title: 组件
  order: 1
group:
  title: Data Entry
  order: 1
title: PisellLookup
order: 1
category: pro
---

# PisellLookup 搜索组件

## 组件介绍

PisellLookup 是 Pisell 2.0 组件库中的**搜索交互协议组件**。它提供了统一的搜索入口、状态管理和结果承载能力，将搜索交互与业务逻辑完全解耦。

**核心特性**：

- ✅ 多种触发器：Input、Button、Icon、Text、自定义 ReactNode
- ✅ 多种展示方式：Inline、Popup、Modal、Drawer、Detached
- ✅ 搜索历史：自动保存和管理搜索记录
- ✅ 联想词：支持异步加载和显示
- ✅ 热门搜索：推荐搜索关键词
- ✅ 扫码支持：集成扫码功能
- ✅ 实时/确认搜索：灵活的搜索触发方式
- ✅ 受控和非受控模式
- ✅ 基于 Ant Design 开发

**设计理念**：

PisellLookup 是一个"容器组件"，类似于 Modal 或 Drawer，它只负责搜索交互流程，而搜索结果的渲染由外部通过 `resultRender`
注入。这种设计使得组件可以适配任何业务场景（商品搜索、用户搜索、订单搜索等）。

---

## 快速开始

### 最简单的用法

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

export default () => {
    return (
        <PisellLookup
            placeholder="搜索商品"
            onSearch={(keyword) => {
                console.log('搜索:', keyword);
            }}
            resultRender={(keyword) => (
                <div>搜索结果: {keyword}</div>
            )}
        />
    );
};
```

### 仅触发搜索（不展示结果）

不提供 `resultRender`，组件只提供搜索入口，搜索结果展示在页面其他位置。

```tsx
import {PisellLookup} from '@pisell/materials';
import {useState} from 'react';
import {List} from 'antd';

const mockSearch = (keyword: string) => {
    const all = ['iPhone 15', 'MacBook Pro', 'iPad Air'];
    if (!keyword) return all;
    return all.filter((item) => item.toLowerCase().includes(keyword.toLowerCase()));
};

export default () => {
    const [searchResults, setSearchResults] = useState<string[]>([]);

    return (
        <div>
            {/* 搜索入口 - 不展示结果 */}
            <PisellLookup
                placeholder="搜索商品"
                onSearch={(keyword) => {
                    // 在页面其他位置展示结果
                    const results = mockSearch(keyword);
                    setSearchResults(results);
                }}
            />

            {/* 搜索结果展示在页面下方 */}
            <List
                style={{marginTop: 20}}
                dataSource={searchResults}
                renderItem={(item) => <List.Item>{item}</List.Item>}
            />
        </div>
    );
};
```

### 基础示例（Input + Inline）

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';
import {useState} from 'react';

export default () => {
    const products = [
        {id: 1, name: 'iPhone 15 Pro', price: 7999},
        {id: 2, name: 'MacBook Pro', price: 12999},
        {id: 3, name: 'iPad Air', price: 4799},
    ];

    return (
        <PisellLookup
            placeholder="搜索商品"
            displayType="inline"
            onSearch={(keyword) => {
                console.log('搜索:', keyword);
            }}
            resultRender={(keyword) => {
                const results = products.filter(p =>
                    p.name.toLowerCase().includes(keyword.toLowerCase())
                );

                return (
                    <List
                        dataSource={results}
                        renderItem={(item) => (
                            <List.Item>
                                <List.Item.Meta
                                    title={item.name}
                                    description={`¥${item.price}`}
                                />
                            </List.Item>
                        )}
                    />
                );
            }}
        />
    );
};
```

---

## 触发器类型

### Input 触发器

最常用的触发方式，适合轻量级搜索场景。

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

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            placeholder="搜索商品"
            size="default"
            showPrefixIcon={true}
            showClear={true}
            showConfirmButton={true}
            confirmButtonText="Lookup"
            showScan={true}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### Button 触发器

适合需要明确触发动作的场景。

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

export default () => {
    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索商品"
            displayType="modal"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### Icon 触发器

适合空间受限的场景。

```tsx
import {PisellLookup} from '@pisell/materials';
import {SearchOutlined} from '@ant-design/icons';

export default () => {
    return (
        <PisellLookup
            triggerType="icon"
            triggerIcon={<SearchOutlined style={{fontSize: 24}}/>}
            displayType="modal"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### Text 触发器

适合需要文本链接的场景。

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

export default () => {
    return (
        <PisellLookup
            triggerType="text"
            triggerText="点击搜索"
            displayType="modal"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 自定义触发器

支持任意 ReactNode 作为触发器。

```tsx
import {PisellLookup} from '@pisell/materials';
import {Card, Space} from 'antd';
import {SearchOutlined} from '@ant-design/icons';

export default () => {
    return (
        <PisellLookup
            trigger={
                <Card hoverable style={{width: 200}}>
                    <Space>
                        <SearchOutlined/>
                        <span>高级搜索</span>
                    </Space>
                </Card>
            }
            displayType="modal"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

---

## 展示方式

### Inline 内联展示

结果在触发器下方直接展开，适合列表页快速搜索。

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';

export default () => {
    const mockData = ['iPhone 15', 'MacBook Pro', 'iPad Air'];

    return (
        <PisellLookup
            triggerType="input"
            displayType="inline"
            placeholder="搜索商品"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => (
                <List
                    dataSource={mockData.filter(item =>
                        item.toLowerCase().includes(keyword.toLowerCase())
                    )}
                    renderItem={(item) => <List.Item>{item}</List.Item>}
                />
            )}
        />
    );
};
```

### Modal 弹窗展示

结果在弹窗中展示，适合复杂搜索场景。

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';

export default () => {
    const mockData = ['iPhone 15', 'MacBook Pro', 'iPad Air'];

    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索"
            displayType="modal"
            modalWidth={834}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => (
                <List
                    dataSource={mockData}
                    renderItem={(item) => <List.Item>{item}</List.Item>}
                />
            )}
        />
    );
};
```

### Drawer 抽屉展示

结果在抽屉中展示，适合移动端或侧边栏场景。

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';

export default () => {
    const mockData = ['iPhone 15', 'MacBook Pro', 'iPad Air'];

    return (
        <PisellLookup
            triggerType="button"
            buttonText="打开搜索"
            displayType="drawer"
            drawerWidth={500}
            drawerPlacement="right"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => (
                <List
                    dataSource={mockData}
                    renderItem={(item) => <List.Item>{item}</List.Item>}
                />
            )}
        />
    );
};
```

---

## 搜索模式

### 实时搜索模式

输入内容后自动触发搜索（带防抖），包括清空输入时也会触发（用于恢复数据）。

```tsx
import {PisellLookup} from '@pisell/materials';
import {useState} from 'react';
import {List} from 'antd';

export default () => {
    const [data] = useState([
        {id: 1, name: 'iPhone 15 Pro'},
        {id: 2, name: 'MacBook Pro'},
        {id: 3, name: 'iPad Air'},
    ]);
    const [filteredData, setFilteredData] = useState(data);

    return (
        <div>
            <PisellLookup
                triggerType="input"
                searchMode="realtime"
                debounceTime={300}
                placeholder="输入即搜索（300ms 防抖）"
                onSearch={(keyword) => {
                    if (keyword) {
                        // 有关键词：过滤数据
                        const filtered = data.filter(item => item.name.includes(keyword));
                        setFilteredData(filtered);
                    } else {
                        // 空关键词：恢复显示所有数据
                        setFilteredData(data);
                    }
                }}
            />

            <List
                dataSource={filteredData}
                renderItem={(item) => <List.Item>{item.name}</List.Item>}
            />
        </div>
    );
};
```

### 确认触发模式

点击按钮或按 Enter 键触发搜索。

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

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            searchMode="confirm"
            placeholder="点击 Lookup 或按 Enter 搜索"
            showConfirmButton={true}
            confirmButtonText="Search"
            onSearch={(keyword) => console.log('确认搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 隐藏 Lookup 按钮（输入即搜索）

隐藏 Lookup 按钮后，自动使用实时搜索模式（输入即触发）。

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

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            placeholder="输入即搜索"
            showConfirmButton={false}
            // searchMode 会自动变为 'realtime'
            onSearch={(keyword) => console.log('实时搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

---

## 辅助功能

### 搜索历史

自动保存搜索关键词到 localStorage。

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

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            placeholder="搜索商品（支持历史记录）"
            displayType="inline"
            showHistory={true}
            historyKey="product_search_history"
            maxHistoryCount={10}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 联想词

根据输入内容显示联想词。

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

export default () => {
    const mockProducts = ['iPhone 15 Pro', 'iPhone 14', 'iPad Air', 'MacBook Pro'];

    return (
        <PisellLookup
            triggerType="input"
            placeholder="输入查看联想词"
            displayType="inline"
            showSuggestions={true}
            onLoadSuggestions={(keyword) => {
                // 返回匹配的联想词
                return mockProducts.filter(p =>
                    p.toLowerCase().includes(keyword.toLowerCase())
                );
            }}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 热门搜索

展示推荐的热门搜索关键词。

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

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            placeholder="查看热门搜索"
            displayType="inline"
            showHotWords={true}
            onLoadHotWords={() => {
                // 返回热门搜索关键词
                return ['iPhone', 'MacBook', 'iPad', 'AirPods', 'Apple Watch'];
            }}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 完整功能（历史 + 联想词 + 热门搜索）

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';

export default () => {
    const mockProducts = ['iPhone 15 Pro', 'iPhone 14', 'iPad Air', 'MacBook Pro'];
    const hotWords = ['iPhone', 'MacBook', 'iPad', 'AirPods'];

    return (
        <PisellLookup
            triggerType="input"
            placeholder="搜索商品（完整功能）"
            displayType="inline"
            // 搜索历史
            showHistory={true}
            historyKey="demo_history"
            maxHistoryCount={10}
            // 联想词
            showSuggestions={true}
            onLoadSuggestions={(keyword) => {
                return mockProducts.filter(p =>
                    p.toLowerCase().includes(keyword.toLowerCase())
                );
            }}
            // 热门搜索
            showHotWords={true}
            onLoadHotWords={() => hotWords}
            // 搜索回调
            onSearch={(keyword) => console.log('搜索:', keyword)}
            // 结果渲染
            resultRender={(keyword) => (
                <List
                    dataSource={mockProducts.filter(p =>
                        p.toLowerCase().includes(keyword.toLowerCase())
                    )}
                    renderItem={(item) => <List.Item>{item}</List.Item>}
                />
            )}
        />
    );
};
```

---

## 受控模式

### 受控关键词

```tsx
import {PisellLookup} from '@pisell/materials';
import {useState} from 'react';
import {Space, Button} from 'antd';

export default () => {
    const [keyword, setKeyword] = useState('');

    return (
        <div>
            <Space style={{marginBottom: 16}}>
                <Button onClick={() => setKeyword('iPhone')}>设置为 iPhone</Button>
                <Button onClick={() => setKeyword('MacBook')}>设置为 MacBook</Button>
                <Button onClick={() => setKeyword('')}>清空</Button>
            </Space>

            <PisellLookup
                value={keyword}
                onChange={setKeyword}
                placeholder="受控模式"
                onSearch={(kw) => console.log('搜索:', kw)}
                resultRender={(kw) => <div>搜索: {kw}</div>}
            />
        </div>
    );
};
```

### 受控可见性

```tsx
import {PisellLookup} from '@pisell/materials';
import {useState} from 'react';
import {Button, Space} from 'antd';

export default () => {
    const [visible, setVisible] = useState(false);

    return (
        <div>
            <Space style={{marginBottom: 16}}>
                <Button onClick={() => setVisible(true)}>打开搜索</Button>
                <Button onClick={() => setVisible(false)}>关闭搜索</Button>
                <Button onClick={() => setVisible(!visible)}>切换</Button>
            </Space>

            <PisellLookup
                triggerType="input"
                displayType="inline"
                visible={visible}
                onVisibleChange={setVisible}
                placeholder="受控可见性"
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
        </div>
    );
};
```

### 使用 Ref 控制

通过 ref 调用方法控制组件。

```tsx
import {PisellLookup, PisellLookupRef} from '@pisell/materials';
import {useRef} from 'react';
import {Button, Space} from 'antd';

export default () => {
    const lookupRef = useRef<PisellLookupRef>(null);

    return (
        <div>
            <Space style={{marginBottom: 16}}>
                <Button onClick={() => lookupRef.current?.open()}>打开搜索</Button>
                <Button onClick={() => lookupRef.current?.close()}>关闭搜索</Button>
                <Button onClick={() => {
                    lookupRef.current?.setKeyword('iPhone');
                }}>设置关键词</Button>
                <Button onClick={() => {
                    const keyword = lookupRef.current?.getKeyword();
                    alert(`当前关键词: ${keyword}`);
                }}>获取关键词</Button>
            </Space>

            <PisellLookup
                ref={lookupRef}
                triggerType="button"
                buttonText="搜索"
                displayType="modal"
                onSearch={(keyword) => console.log('搜索:', keyword)}
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
        </div>
    );
};
```

### 监听打开和关闭事件

使用 onOpen 和 onClose 回调监听 Modal/Drawer 的打开和关闭。

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

export default () => {
    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索"
            displayType="modal"
            onOpen={() => {
                console.log('搜索面板已打开');
                message.info('搜索面板已打开');
            }}
            onClose={() => {
                console.log('搜索面板已关闭');
                message.info('搜索面板已关闭');
            }}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### 关闭时清空内容

如果需要关闭时清空关键词（每次打开都是空白状态），可以设置 `clearOnClose={true}`。

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

export default () => {
    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索商品"
            displayType="modal"
            clearOnClose={true}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

---

## 自定义渲染

### 自定义结果渲染

```tsx
import {PisellLookup} from '@pisell/materials';
import {Card, Tag, Space} from 'antd';

export default () => {
    const products = [
        {id: 1, name: 'iPhone 15 Pro', price: 7999, category: '手机'},
        {id: 2, name: 'MacBook Pro', price: 12999, category: '电脑'},
    ];

    return (
        <PisellLookup
            triggerType="input"
            placeholder="搜索商品"
            displayType="inline"
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword, onClose) => {
                const results = products.filter(p =>
                    p.name.toLowerCase().includes(keyword.toLowerCase())
                );

                return (
                    <Space direction="vertical" style={{width: '100%'}}>
                        {results.map(item => (
                            <Card key={item.id} size="small">
                                <Space direction="vertical">
                                    <strong>{item.name}</strong>
                                    <Space>
                                        <Tag color="blue">{item.category}</Tag>
                                        <span style={{color: '#ff4d4f'}}>¥{item.price}</span>
                                    </Space>
                                </Space>
                            </Card>
                        ))}
                    </Space>
                );
            }}
        />
    );
};
```

### 自定义历史记录渲染

```tsx
import {PisellLookup} from '@pisell/materials';
import {Button, Space} from 'antd';

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            displayType="inline"
            showHistory={true}
            historyRender={(history, onSelect) => (
                <div>
                    <h4>我的搜索记录</h4>
                    <Space wrap>
                        {history.map((item, index) => (
                            <Button
                                key={index}
                                size="small"
                                onClick={() => onSelect(item)}
                            >
                                {item}
                            </Button>
                        ))}
                    </Space>
                </div>
            )}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

---

## 容器样式配置

### Modal 自定义宽度

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

export default () => {
    return (
        <PisellLookup
            triggerType="button"
            buttonText="打开宽屏搜索"
            displayType="modal"
            modalWidth={1200}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### Modal 显示遮罩层

默认不显示遮罩层，如需显示可以设置 `mask={true}`。

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

export default () => {
    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索（带遮罩）"
            displayType="modal"
            mask={true}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => <div>搜索: {keyword}</div>}
        />
    );
};
```

### Modal 自定义挂载点并使用 100% 宽度

当 Modal 挂载到特定容器时，可以使用百分比宽度填满容器。

**注意：** 挂载容器需要设置 `position: relative` 或 `position: absolute`。

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

export default () => {
    return (
        <div>
            {/* 自定义容器 - 必须设置 position: relative */}
            <div
                id="modal-container"
                style={{
                    width: 800,
                    height: 600,
                    position: 'relative'  // 重要：必须设置定位
                }}
            >
                {/* 其他内容 */}
            </div>

            <PisellLookup
                triggerType="button"
                buttonText="搜索"
                displayType="modal"
                getContainer={() => document.getElementById('modal-container')}
                modalWidth="100%"
                onSearch={(keyword) => console.log('搜索:', keyword)}
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
        </div>
    );
};
```

### Modal 自定义挂载点

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

export default () => {
    return (
        <div>
            <div id="modal-container"></div>

            <PisellLookup
                triggerType="button"
                buttonText="搜索"
                displayType="modal"
                getContainer={() => document.getElementById('modal-container')}
                onSearch={(keyword) => console.log('搜索:', keyword)}
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
        </div>
    );
};
```

### Drawer 不同方向

```tsx
import {PisellLookup} from '@pisell/materials';
import {Space} from 'antd';

export default () => {
    return (
        <Space>
            <PisellLookup
                triggerType="button"
                buttonText="右侧"
                displayType="drawer"
                drawerPlacement="right"
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />

            <PisellLookup
                triggerType="button"
                buttonText="左侧"
                displayType="drawer"
                drawerPlacement="left"
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />

            <PisellLookup
                triggerType="button"
                buttonText="顶部"
                displayType="drawer"
                drawerPlacement="top"
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />

            <PisellLookup
                triggerType="button"
                buttonText="底部"
                displayType="drawer"
                drawerPlacement="bottom"
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
        </Space>
    );
};
```

---

## 加载状态与空状态

### 加载状态

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

export default () => {
    const [loading, setLoading] = useState(false);

    return (
        <PisellLookup
            triggerType="input"
            displayType="inline"
            placeholder="搜索商品"
            loading={loading}
            onSearch={(keyword) => {
                setLoading(true);
                // 模拟异步搜索
                setTimeout(() => setLoading(false), 1000);
            }}
            resultRender={(keyword) => <div>搜索结果: {keyword}</div>}
        />
    );
};
```

### 自定义空状态

```tsx
import {PisellLookup} from '@pisell/materials';
import {Empty} from 'antd';
import {SearchOutlined} from '@ant-design/icons';

export default () => {
    return (
        <PisellLookup
            triggerType="input"
            displayType="inline"
            defaultVisible={true}
            placeholder="搜索商品"
            emptyRender={() => (
                <Empty
                    image={<SearchOutlined style={{fontSize: 48, color: '#ccc'}}/>}
                    description="请输入搜索内容"
                />
            )}
        />
    );
};
```

---

## 实际应用场景

### 商品列表快速搜索（结果在列表中展示）

不提供 `resultRender`，搜索结果直接过滤列表数据。

```tsx
import {PisellLookup} from '@pisell/materials';
import {Table, Tag} from 'antd';
import {useState} from 'react';

export default () => {
    const [data] = useState([
        {id: 1, name: 'iPhone 15 Pro', price: 7999, category: '手机', stock: 100},
        {id: 2, name: 'MacBook Pro', price: 12999, category: '电脑', stock: 50},
        {id: 3, name: 'iPad Air', price: 4799, category: '平板', stock: 80},
    ]);

    const [filteredData, setFilteredData] = useState(data);

    return (
        <div>
            {/* 搜索框 - 不展示结果 */}
            <PisellLookup
                triggerType="input"
                placeholder="搜索商品名称"
                showConfirmButton={false}
                onSearch={(keyword) => {
                    const filtered = data.filter(item =>
                        item.name.toLowerCase().includes(keyword.toLowerCase())
                    );
                    setFilteredData(filtered);
                }}
            />

            {/* 结果在表格中展示 */}
            <Table
                dataSource={filteredData}
                columns={[
                    {title: '商品名称', dataIndex: 'name'},
                    {title: '价格', dataIndex: 'price', render: (v) => `¥${v}`},
                    {title: '分类', dataIndex: 'category', render: (v) => <Tag>{v}</Tag>},
                    {title: '库存', dataIndex: 'stock'},
                ]}
                style={{marginTop: 16}}
            />
        </div>
    );
};
```

### 弹窗选择用户

```tsx
import {PisellLookup} from '@pisell/materials';
import {Table, Button, Tag} from 'antd';
import {useState} from 'react';

export default () => {
    const [selectedUser, setSelectedUser] = useState(null);

    const users = [
        {id: 1, name: 'Olivia Rhye', email: 'olivia@example.com', role: 'Admin'},
        {id: 2, name: 'Phoenix Baker', email: 'phoenix@example.com', role: 'User'},
        {id: 3, name: 'Liam Carter', email: 'liam@example.com', role: 'User'},
    ];

    return (
        <div>
            <div style={{marginBottom: 16}}>
                已选择: {selectedUser ? selectedUser.name : '无'}
            </div>

            <PisellLookup
                triggerType="button"
                buttonText="选择用户"
                displayType="modal"
                modalWidth={834}
                showHistory={true}
                onSearch={(keyword) => console.log('搜索用户:', keyword)}
                resultRender={(keyword, onClose) => {
                    const results = users.filter(u =>
                        u.name.toLowerCase().includes(keyword.toLowerCase()) ||
                        u.email.toLowerCase().includes(keyword.toLowerCase())
                    );

                    return (
                        <Table
                            dataSource={results}
                            columns={[
                                {title: '姓名', dataIndex: 'name'},
                                {title: '邮箱', dataIndex: 'email'},
                                {title: '角色', dataIndex: 'role', render: (v) => <Tag>{v}</Tag>},
                                {
                                    title: '操作',
                                    render: (_, record) => (
                                        <Button
                                            size="small"
                                            onClick={() => {
                                                setSelectedUser(record);
                                                onClose();
                                            }}
                                        >
                                            选择
                                        </Button>
                                    ),
                                },
                            ]}
                        />
                    );
                }}
            />
        </div>
    );
};
```

### 移动端搜索

```tsx
import {PisellLookup} from '@pisell/materials';
import {List} from 'antd';

export default () => {
    const products = ['iPhone 15', 'MacBook Pro', 'iPad Air', 'AirPods Pro'];

    return (
        <PisellLookup
            triggerType="button"
            buttonText="搜索"
            size="large"
            displayType="drawer"
            drawerWidth="100%"
            drawerPlacement="bottom"
            showHistory={true}
            showHotWords={true}
            onLoadHotWords={() => ['iPhone', 'MacBook', 'iPad']}
            onSearch={(keyword) => console.log('搜索:', keyword)}
            resultRender={(keyword) => (
                <List
                    dataSource={products.filter(p =>
                        p.toLowerCase().includes(keyword.toLowerCase())
                    )}
                    renderItem={(item) => (
                        <List.Item style={{padding: '16px'}}>
                            <strong style={{fontSize: 16}}>{item}</strong>
                        </List.Item>
                    )}
                />
            )}
        />
    );
};
```

---

## API 文档

### Props 属性

#### 触发器配置

| 属性名               | 类型                                        | 默认值         | 说明                            |
|-------------------|-------------------------------------------|-------------|-------------------------------|
| triggerType       | `'input' \| 'button' \| 'icon' \| 'text'` | `'input'`   | 预设触发器类型                       |
| trigger           | `ReactNode`                               | -           | 自定义触发器（与 triggerType 互斥）      |
| placeholder       | `string`                                  | `'请输入搜索内容'` | 占位符文本                         |
| buttonText        | `string`                                  | `'搜索'`      | 按钮文本（triggerType='button' 时）  |
| triggerText       | `string`                                  | -           | 文本触发器文本（triggerType='text' 时） |
| triggerIcon       | `ReactNode`                               | -           | 图标触发器（triggerType='icon' 时）   |
| size              | `'large' \| 'middle' \| 'small'`          | `'middle'`  | 输入框尺寸                         |
| disabled          | `boolean`                                 | `false`     | 是否禁用                          |
| showPrefixIcon    | `boolean`                                 | `false`     | 是否显示前缀图标                      |
| prefixIcon        | `ReactNode`                               | -           | 前缀图标                          |
| showClear         | `boolean`                                 | `true`      | 是否显示清除按钮                      |
| showConfirmButton | `boolean`                                 | `true`      | 是否显示确认按钮                      |
| confirmButtonText | `string`                                  | `'Lookup'`  | 确认按钮文本                        |

#### 搜索行为配置

| 属性名          | 类型                        | 默认值          | 说明         |
|--------------|---------------------------|--------------|------------|
| searchMode   | `'realtime' \| 'confirm'` | `'realtime'` | 搜索触发模式     |
| debounceTime | `number`                  | `300`        | 防抖时间（毫秒）   |
| value        | `string`                  | -            | 搜索关键词（受控）  |
| defaultValue | `string`                  | -            | 默认关键词（非受控） |
| showScan     | `boolean`                 | `true`       | 是否显示扫码按钮   |

#### 搜索历史配置

| 属性名             | 类型                                                                      | 默认值                       | 说明         |
|-----------------|-------------------------------------------------------------------------|---------------------------|------------|
| showHistory     | `boolean`                                                               | `false`                   | 是否显示搜索历史   |
| historyKey      | `string`                                                                | `'pisell_lookup_history'` | 历史记录存储 key |
| maxHistoryCount | `number`                                                                | `10`                      | 历史记录数量上限   |
| onLoadHistory   | `() => string[]`                                                        | -                         | 自定义加载历史记录  |
| onSaveHistory   | `(history: string[]) => void`                                           | -                         | 自定义保存历史记录  |
| historyRender   | `(history: string[], onSelect: (keyword: string) => void) => ReactNode` | -                         | 自定义历史记录渲染  |

#### 联想词配置

| 属性名               | 类型                                                                          | 默认值     | 说明       |
|-------------------|-----------------------------------------------------------------------------|---------|----------|
| showSuggestions   | `boolean`                                                                   | `false` | 是否显示联想词  |
| onLoadSuggestions | `(keyword: string) => Promise<string[]> \| string[]`                        | -       | 加载联想词回调  |
| suggestionsRender | `(suggestions: string[], onSelect: (keyword: string) => void) => ReactNode` | -       | 自定义联想词渲染 |

#### 热门搜索配置

| 属性名            | 类型                                                                       | 默认值     | 说明        |
|----------------|--------------------------------------------------------------------------|---------|-----------|
| showHotWords   | `boolean`                                                                | `false` | 是否显示热门搜索  |
| onLoadHotWords | `() => Promise<string[]> \| string[]`                                    | -       | 加载热门搜索回调  |
| hotWordsRender | `(hotWords: string[], onSelect: (keyword: string) => void) => ReactNode` | -       | 自定义热门搜索渲染 |

#### 结果展示配置

| 属性名          | 类型                                                         | 默认值     | 说明                          |
|--------------|------------------------------------------------------------|---------|-----------------------------|
| displayType  | `'inline' \| 'popup' \| 'modal' \| 'drawer' \| 'detached'` | 智能判断    | 结果展示方式                      |
| resultRender | `(keyword: string, onClose?: () => void) => ReactNode`     | -       | 结果渲染函数（可选，不提供时只触发 onSearch） |
| loading      | `boolean`                                                  | `false` | 是否显示加载状态                    |
| emptyRender  | `() => ReactNode`                                          | -       | 空状态渲染                       |

#### 容器样式配置

| 属性名                | 类型                                            | 默认值       | 说明                                |
|--------------------|-----------------------------------------------|-----------|-----------------------------------|
| modalWidth         | `number \| string`                            | `834`     | Modal 宽度（支持数字或百分比，如 834 或 "100%"） |
| modalStyle         | `CSSProperties`                               | -         | Modal 自定义样式                       |
| getContainer       | `HTMLElement \| (() => HTMLElement) \| false` | -         | Modal 挂载节点                        |
| drawerWidth        | `number \| string`                            | `378`     | Drawer 宽度                         |
| drawerPlacement    | `'left' \| 'right' \| 'top' \| 'bottom'`      | `'right'` | Drawer 位置                         |
| popupWidth         | `number \| string`                            | -         | Popup 宽度                          |
| containerStyle     | `CSSProperties`                               | -         | 容器自定义样式                           |
| containerClassName | `string`                                      | -         | 容器自定义类名                           |

#### 可见性控制

| 属性名            | 类型        | 默认值     | 说明                        |
|----------------|-----------|---------|---------------------------|
| visible        | `boolean` | -       | 是否可见（受控）                  |
| defaultVisible | `boolean` | `false` | 默认是否可见（非受控）               |
| clearOnClose   | `boolean` | `false` | 关闭时是否清空关键词                |
| mask           | `boolean` | `false` | 是否显示遮罩层（Modal/Drawer）     |
| maskClosable   | `boolean` | `true`  | 点击遮罩是否关闭（仅 mask=true 时生效） |
| zIndex         | `number`  | -       | 层级                        |

#### 其他配置

| 属性名       | 类型              | 默认值 | 说明    |
|-----------|-----------------|-----|-------|
| className | `string`        | -   | 自定义类名 |
| style     | `CSSProperties` | -   | 自定义样式 |

### 事件回调

| 事件名             | 类型                           | 说明                       |
|-----------------|------------------------------|--------------------------|
| onChange        | `(value: string) => void`    | 关键词变化回调                  |
| onSearch        | `(keyword: string) => void`  | 搜索触发回调                   |
| onScan          | `(code: string) => void`     | 扫码完成回调                   |
| onVisibleChange | `(visible: boolean) => void` | 可见性变化回调                  |
| onOpen          | `() => void`                 | 打开回调（Modal/Drawer 打开时触发） |
| onClose         | `() => void`                 | 关闭回调（Modal/Drawer 关闭时触发） |

### Ref 方法

通过 ref 可以调用以下方法：

| 方法名        | 类型                          | 说明      |
|------------|-----------------------------|---------|
| open       | `() => void`                | 打开搜索    |
| close      | `() => void`                | 关闭搜索    |
| getKeyword | `() => string`              | 获取当前关键词 |
| setKeyword | `(keyword: string) => void` | 设置关键词   |

**使用示例：**

```tsx
import { useRef } from 'react';
import { PisellLookup, type PisellLookupRef } from '@pisell/materials';

export default () => {
    const lookupRef = useRef<PisellLookupRef>(null);

    return (
        <div>
            <PisellLookup
                ref={lookupRef}
                placeholder="搜索"
                onSearch={(keyword) => console.log(keyword)}
                resultRender={(keyword) => <div>搜索: {keyword}</div>}
            />
            {/* 打开: lookupRef.current?.open(); 关闭: lookupRef.current?.close();
                设置关键词: lookupRef.current?.setKeyword('iPhone');
                获取关键词: lookupRef.current?.getKeyword(); */}
        </div>
    );
};
```

---

## 智能默认值

PisellLookup 会根据触发器类型智能判断展示方式：

| 触发器类型                  | 默认展示方式                 |
|------------------------|------------------------|
| `triggerType="input"`  | `displayType="inline"` |
| `triggerType="button"` | `displayType="modal"`  |
| `triggerType="icon"`   | `displayType="modal"`  |
| `triggerType="text"`   | `displayType="modal"`  |
| `trigger={<Custom />}` | `displayType="modal"`  |

---

## 常见问题

### 1. 如何控制搜索结果的显示和隐藏？

使用 `visible` 和 `onVisibleChange` 实现受控：

```
const [visible, setVisible] = useState(false);
<PisellLookup visible={visible} onVisibleChange={setVisible} />
```

### 2. 搜索历史保存在哪里？

默认保存在 `localStorage`，可以通过以下方式自定义：

```
<PisellLookup
  showHistory={true}
  historyKey="my_custom_key"
  onLoadHistory={() => {/* 自定义加载逻辑 */}}
  onSaveHistory={(history) => {/* 自定义保存逻辑 */}}
/>
```

### 3. 如何实现异步搜索？

在 `onSearch` 回调中处理异步逻辑，使用 `loading` 控制加载状态：

```
const [loading, setLoading] = useState(false);

<PisellLookup
  loading={loading}
  onSearch={async (keyword) => {
    setLoading(true);
    try {
      const results = await fetchSearchResults(keyword);
      // 处理结果
    } finally {
      setLoading(false);
    }
  }}
/>
```

### 4. 联想词和热门搜索如何互斥显示？

组件会自动处理：

- 无输入时显示**热门搜索**
- 有输入时显示**联想词**

### 5. 如何自定义 Modal 的位置和宽度？

使用 `getContainer` 和 `modalWidth` 属性：

```
// 挂载到指定容器
<PisellLookup
    displayType="modal"
    getContainer={() => document.getElementById('container')}
/>

// 挂载到触发器父节点
<PisellLookup
    displayType="modal"
    getContainer={false}
/>

// 挂载到指定容器，并使用 100% 宽度填满容器
<div id="custom-container" style={{width: 800}}>
    <PisellLookup
        displayType="modal"
        getContainer={() => document.getElementById('custom-container')}
        modalWidth="100%"
    />
</div>

// 挂载到指定容器，并使用自定义宽度
<PisellLookup
    displayType="modal"
    getContainer={() => document.getElementById('container')}
    modalWidth={600}
/>
```

**modalWidth 支持的格式：**

- 数字：`modalWidth={834}` → 834px
- 百分比：`modalWidth="100%"` → 100% 宽度
- 其他 CSS 单位：`modalWidth="50vw"` → 50% 视口宽度

### 6. 实时搜索和确认触发有什么区别？

- **实时搜索** (`searchMode="realtime"`)：输入后自动触发（带防抖），不保存历史
- **确认触发** (`searchMode="confirm"`)：点击按钮或按 Enter 触发，保存历史

### 7. 什么时候会保存搜索历史？

只在以下情况保存历史记录：

- 点击 Lookup 确认按钮
- 按 Enter 键
- 点击搜索历史记录
- 点击联想词
- 点击热门搜索
- 扫码后触发搜索

实时搜索的防抖触发**不会**保存历史。

### 8. 为什么默认不显示遮罩层？

默认 `mask={false}` 不显示遮罩层，这样用户可以在搜索的同时查看页面其他内容，提供更好的体验。

**默认行为：**

- ✅ 不显示遮罩层（背景不变暗）
- ✅ 点击背后的内容不会关闭 Modal
- ✅ 只能通过 Close 按钮或 ESC 键关闭

如果需要强制用户专注于搜索，可以设置 `mask={true}`：

```
<PisellLookup
  displayType="modal"
  mask={true}  // 显示遮罩层
  maskClosable={true}  // 点击遮罩可关闭
/>
```

**注意：** `maskClosable` 只在 `mask={true}` 时生效。

### 9. 如何隐藏 Lookup 按钮并实现输入即搜索？

设置 `showConfirmButton={false}`，组件会自动切换为实时搜索模式：

```
<PisellLookup
  showConfirmButton={false}
  debounceTime={300}
  // searchMode 会自动变为 'realtime'
/>
```

**智能判断逻辑：**

- `showConfirmButton={true}` → 使用配置的 searchMode
- `showConfirmButton={false}` → 强制使用 realtime 模式

这在以下场景很有用：

- 极简输入框，只保留搜索图标和清除按钮
- 移动端场景，减少按钮占用空间
- 希望更流畅的搜索体验

### 10. 如何使用扫码功能？

扫码功能已完全集成 PisellQRScanner 组件，开箱即用：

```
<PisellLookup
    showScan={true}
    onScan={(code) => {
        console.log('扫码结果:', code);
        // 扫码结果会自动填入搜索框并触发搜索
    }}
/>
```

**功能特点：**

- ✅ 点击扫码图标自动打开扫码界面
- ✅ 支持摄像头实时扫码
- ✅ 支持上传图片识别二维码
- ✅ 扫码成功后自动填入搜索框
- ✅ 自动触发搜索并保存到历史记录

**隐藏扫码按钮：**

```
<PisellLookup
    showScan={false}
/>
```

### 11. 如何在页面其他位置展示搜索结果？

不提供 `resultRender` 属性，组件只提供搜索入口功能，搜索结果可以展示在页面任意位置：

```
const [results, setResults] = useState([]);

<PisellLookup
  placeholder="搜索商品"
  onSearch={(keyword) => {
    const filtered = data.filter(item => item.name.includes(keyword));
    setResults(filtered);
  }}
  // 不提供 resultRender
/>

{/* 结果展示在页面下方 */}
<List dataSource={results} />
```

这种方式适合：

- 列表页的顶部搜索框，结果展示在列表中
- 需要复杂的结果布局，不适合在弹窗中展示
- 搜索结果需要与页面其他内容联动

### 12. 关闭后是否保留搜索内容？

**默认行为**：`clearOnClose={false}`，关闭后**保留关键词**，再次打开时**自动选中文字**。

```
<PisellLookup
  triggerType="button"
  displayType="modal"
  // clearOnClose={false}  // 默认值，保留搜索内容
/>
```

**默认保留内容的好处：**

- ✅ 用户多次使用相同搜索关键词时更便捷
- ✅ 临时关闭搜索查看其他内容，再次打开继续搜索
- ✅ 自动选中文字，方便快速修改或替换
- ✅ 提升用户体验，避免重复输入

如果需要关闭时清空关键词，可以设置 `clearOnClose={true}`：

```
<PisellLookup
  triggerType="button"
  displayType="modal"
  clearOnClose={true}  // 关闭时清空
/>
```

---

## 最佳实践

### 1. 触发器选择

- **Input 触发器**：列表页快速搜索、过滤场景
- **Button 触发器**：需要明确搜索动作的场景
- **Icon 触发器**：工具栏、导航栏等空间受限场景
- **自定义触发器**：品牌化、特殊交互需求

### 2. 展示方式选择

- **Inline**：列表页内嵌搜索，轻量级场景
- **Modal**：复杂搜索、多条件筛选、需要专注的场景
- **Drawer**：移动端、侧边栏、渐进式展示
- **Detached**：独立的搜索页面

### 3. 性能优化

- 合理设置 `debounceTime`（推荐 300-500ms）
- 使用 `React.memo` 优化 resultRender
- 大数据量时使用虚拟滚动
- 限制历史记录数量（推荐 10-20 条）

### 4. 用户体验

- 提供清晰的 placeholder
- 显示加载状态
- 提供有意义的空状态
- 移动端使用大号尺寸和 Drawer
- 保存搜索历史提升效率

### 5. 搜索结果渲染

- 使用 `resultRender` 的 `onClose` 参数关闭搜索
- 支持在结果中进行选择、确认等操作
- 根据关键词高亮匹配内容
- 提供结果统计信息

---

## 注意事项

1. **职责边界**：PisellLookup 只负责搜索交互，不负责数据请求。数据请求应在 `onSearch` 回调中处理。

2. **结果渲染**：`resultRender` 是可选的。如果不提供，组件只触发 `onSearch` 回调，不展示结果区域，适合将结果展示在页面其他位置的场景。

3. **结果性能**：如果提供了 `resultRender`，该函数会频繁调用，注意性能优化，避免在其中执行耗时操作。

4. **历史记录**：localStorage 有容量限制（约 5MB），注意控制历史记录数量和大小。

5. **触发器互斥**：`triggerType` 和 `trigger` 互斥，优先使用 `trigger`。

6. **展示类型**：如果不指定 `displayType`，组件会根据触发器类型智能判断，建议显式指定以确保预期行为。

7. **受控模式**：如果同时设置 `value` 和 `defaultValue`，受控模式优先。

8. **搜索历史保存**：只有明确的搜索触发（点击按钮、Enter、选择辅助项）才会保存历史，实时搜索不保存。

9. **搜索模式智能判断**：如果 `showConfirmButton={false}`，会自动使用实时搜索模式，无需手动配置 `searchMode`。

---

## 相关组件

- **PisellInput** - 输入框组件
- **PisellModal** - 弹窗组件
- **PisellDrawer** - 抽屉组件
- **PisellQRScanner** - 扫码组件
- **PisellCards** - 卡片列表组件（可用于渲染搜索结果）
